Compare commits

...

22 Commits

Author SHA1 Message Date
Whit Waldo 6b49bed7f1
Re-enabled the test with a fix to use a fixed 30 days for @monthly in actor deserialization for now (#1530)
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-05-02 17:35:08 -05:00
Whit Waldo faeeb8eaca
Fix for large file cryptography support (#1528)
* Porting the changes from Dapr.Cryptography 1.16 back to 1.15

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-05-02 16:51:41 -05:00
Whit Waldo 0873c5ef6f
Updating gRPC and Microsoft.DurableTask.* packages (#1523)
* Updating gRPC, Google.Protobuf and Microsoft.DurableTask.* packages
2025-04-25 14:45:24 -05:00
Whit Waldo 32d06a7136
Tentative fix for timers deserializing error (#1512)
* Tentative fix for deserializing error
* Added unit tests to prove out timer deserialization for all supported formats

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-04-08 00:17:49 -05:00
Whit Waldo 6f07643280
Refactored to make deserialization for GetJobAsync testable. Added unit test to validate reported customer issue. (#1497)
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-04-01 17:32:17 -05:00
Whit Waldo 55895fa19d
Updated Dapr runtime/CLI version used in integration tests (#1485)
* Updated itests.yml to use latest 1.15 runtime and CLI versions over 1.14 versions
* Updated CLI argument name as `dapr-http-max-request-size` was changed in a recent update included in the CLI

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-03-16 20:14:32 -05:00
Whit Waldo c14fcea0d4
Actor reminder deserialization bugfix (#1483)
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-03-11 13:47:17 -05:00
Whit Waldo bb47132f98
Fixed options passed to conversation API without "conversationId" throwing (#1480)
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-03-04 11:41:05 -06:00
Whit Waldo cb065f2089
Fixed unit tests validating actor reminder deserialization
Updated unit tests for actor reminder deserialization
2025-02-26 07:09:27 -06:00
Whit Waldo 19fd40390b
Fix: Actor reminders should return null if not registered
Added more checks to determine if a reminder was returned or not having discovered that the runtime returns a 200 even if there's no reminder registered. (#1476)
2025-02-26 06:33:52 -06:00
Whit Waldo 94dcdfd5b2
Actor reminders should return null if not registered (#1468)
* The implementation didn't check the status code for whether the actor reminder request failed or not. This updates it to return null if the reminder isn't present.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed spelling error in unit test name

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added unit tests to validate GetReminderAsync request

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated to reflect changed interface

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added unit test to validate null response coming through from mocked ActorTimerManager

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated tests throughout class to use `const string` instead of `var` for constants

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added unit tests to prove out previously implemented reminder retrieval

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-02-26 03:54:03 -06:00
Whit Waldo c94b61e0d6
Fix for Jobs mapping handler (#1474)
Tweaked the jobs mapping handler to accept an optional timeout parameter instead of a cancellation token. This applies the timeout, if specified, to each invocation instead of as a global timeout.

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-02-26 03:40:58 -06:00
Whit Waldo 52f0851780
Removed out-of-date workflow reference in docs (#1463)
Docs: Removed out-of-date workflow reference
2025-02-24 12:38:47 -06:00
Whit Waldo 2b288a1135
Updating Conversation SDK (#1469)
FIX: Updated conversation protos and types to reflect property change from `message` to `content`

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-02-24 11:46:34 -06:00
Whit Waldo 0dc268f501
Fix: Duration-based scheduling is properly formed (#1462)
Fixed: Duration-based scheduling should be properly formatted

When using a duration-based schedule value (e.g. "5s", it's important that the value be preceded by "@every " so it's properly evaluated by the runtime. While a value of "5s" is perfectly valid in the `dueTime` property, the scheduler expects either a Cron expression or a prefixed duration value, necessitating this change.

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-02-20 01:14:13 -06:00
Whit Waldo e9ee4d21bf
Fix for `GetJobAsync` deserialization issue (#1461)
Bugfix: `GetJobAsync` deserialization failure

Updated GetJobsAsync deserialization to reflect actual values returned

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-02-19 13:07:32 -06:00
Whit Waldo 89d9d56bd5
Fixed Jobs SDK bugs (#1456)
fix: Point-in-time not getting scheduled, job payload not being property set on job invocation

When setting a single point-in-time job, the SDK was incorrectly assigning it as a schedule which would promptly fail cron validation. Rather, this now properly sets it to `dueTime` instead. Further, when a Job is invoked, only the payload it was registered with is provided in the callback, not all the elements of a Get Job response, so this was modified to return the `ReadOnlyMemory<byte>` originally provided in the payload back to the caller.

Reviewed by: @philliphoff
Refs: #1455 #1457
2025-02-11 01:16:35 -06:00
Whit Waldo ab3ef305f2
DurableTask package dependency update (#1452)
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-02-03 12:47:05 -06:00
Whit Waldo 9e0672525e
Removed unused reference to FluentAssertions in light of licensing change (#1449)
* Removed unused reference to FluidAssertions in light of licensing change

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed from Dapr.AI.Test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.Extensions.Configuration.Test to use Shouldly

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.Common.Test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.Actors.AspNetCore.Test from FluentAssertions

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added missing copyright header

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.Actors.Test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.Client.Test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed unused using reference

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.E2E.Test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.AspNetCore.Test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Migrated Dapr.AspNetCore.IntegrationTest

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed reference to FluentAssertions

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed several unit tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed another unit test

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed more tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed additional unit tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updating more unit tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed more unit tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed more unit tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed more unit tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-01-30 12:41:06 -06:00
Whit Waldo 676c0d7a7f
Bugfix: Crypto ReadOnlyMemory<byte> decryption times out (#1443)
* Bugfix: Removed use of MemoryMarshal as it wasn't decrypting in-memory byte arrays properly (doesn't impact stream encryption/decryption).

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added extension method similar to how the CommunityToolkit.HighPerformance project handles the creation of MemoryStreams without an allocation. Restored the use of MemoryMarshal, but throws an exception if the data cannot be accessed now, instead of hanging as it did in a previous iteration.

Tested both paths (string and stream) from example project successfully.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added missing using

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-01-15 11:11:26 -06:00
Whit Waldo ef54d75f70
Simplfying Crypto example (#1442)
* Fixed bad console output showing encrypted bytes

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Simplified example so it doesn't require an Azure Key Vault instance and just uses a local set of keys

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated README to include instructions for generating the private key

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added private RSA key to project for users that lack OpenSSL on their system - updated README to include warning calling out that this key shouldn't be used for anything but demonstration and testing purposes.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2025-01-14 17:38:28 -06:00
Hannah Hunter 01b4833474
fix typo (#1439)
Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com>
2025-01-08 16:01:00 -06:00
102 changed files with 3542 additions and 2605 deletions

View File

@ -48,8 +48,8 @@ jobs:
GOOS: linux
GOARCH: amd64
GOPROXY: https://proxy.golang.org
DAPR_CLI_VER: 1.14.0
DAPR_RUNTIME_VER: 1.14.0
DAPR_CLI_VER: 1.15.0
DAPR_RUNTIME_VER: 1.15.3
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.14/install/install.sh
DAPR_CLI_REF: ''
steps:

View File

@ -7,15 +7,14 @@
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="5.9.0" />
<PackageVersion Include="GitHubActionsTestLogger" Version="1.1.2" />
<PackageVersion Include="Google.Api.CommonProtos" Version="2.2.0" />
<PackageVersion Include="Google.Protobuf" Version="3.28.2" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.66.0" />
<PackageVersion Include="Google.Protobuf" Version="3.30.2" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageVersion Include="Grpc.Core.Testing" Version="2.46.6" />
<PackageVersion Include="Grpc.Net.Client" Version="2.66.0" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.66.0" />
<PackageVersion Include="Grpc.Tools" Version="2.67.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.71.0" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.71.0" />
<PackageVersion Include="Grpc.Tools" Version="2.71.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.35" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="6.0.35" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
@ -24,8 +23,8 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.Grpc" Version="1.3.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.Grpc" Version="1.3.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.Grpc" Version="1.10.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.Grpc" Version="1.10.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
@ -43,10 +42,11 @@
<PackageVersion Include="Serilog.AspNetCore" Version="6.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="System.Formats.Asn1" Version="6.0.1" />
<PackageVersion Include="System.Text.Json" Version="6.0.10" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.extensibility.core" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
</Project>

View File

@ -282,26 +282,6 @@ namespace LockService
}
```
### Manage workflow instances (Alpha)
```csharp
var daprClient = new DaprClientBuilder().Build();
string instanceId = "MyWorkflowInstance1";
string workflowComponentName = "dapr"; // alternatively, this could be the name of a workflow component defined in yaml
string workflowName = "MyWorkflowDefinition";
var input = new { name = "Billy", age = 30 }; // Any JSON-serializable value is OK
// Start workflow
var startResponse = await daprClient.StartWorkflowAsync(instanceId, workflowComponentName, workflowName, input);
// Terminate workflow
await daprClient.TerminateWorkflowAsync(instanceId, workflowComponentName);
// Get workflow metadata
var getResponse = await daprClient.GetWorkflowAsync(instanceId, workflowComponentName, workflowName);
```
## Sidecar APIs
### Sidecar Health
The .NET SDK provides a way to poll for the sidecar health, as well as a convenience method to wait for the sidecar to be ready.

View File

@ -10,4 +10,4 @@ With the Dapr Job package, you can interact with the Dapr Job APIs from a .NET a
to run according to a predefined schedule with an optional payload.
To get started, walk through the [Dapr Jobs]({{< ref dotnet-jobs-howto.md >}}) how-to guide and refer to
[best practices documentation]({{< ref dotnet-jobs-usage.md >}}) for additional guidance.
[best practices documentation]({{< ref dotnet-jobsclient-usage.md >}}) for additional guidance.

View File

@ -63,8 +63,8 @@ the dependency injection registration in `Program.cs`, add the following line:
```cs
var builder = WebApplication.CreateBuilder(args);
//Add anywhere between these two
builder.Services.AddDaprJobsClient(); //That's it
//Add anywhere between these two lines
builder.Services.AddDaprJobsClient();
var app = builder.Build();
```
@ -203,7 +203,8 @@ public class MySampleClass
It's easy to set up a jobs endpoint if you're at all familiar with [minimal APIs in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview) as the syntax is the same between the two.
Once dependency injection registration has been completed, configure the application the same way you would to handle mapping an HTTP request via the minimal API functionality in ASP.NET Core. Implemented as an extension method,
pass the name of the job it should be responsive to and a delegate. Services can be injected into the delegate's arguments as you wish and you can optionally pass a `JobDetails` to get information about the job that has been triggered (e.g. access its scheduling setup or payload).
pass the name of the job it should be responsive to and a delegate. Services can be injected into the delegate's arguments as you wish and the job payload can be accessed from the `ReadOnlyMemory<byte>` originally provided to the
job registration.
There are two delegates you can use here. One provides an `IServiceProvider` in case you need to inject other services into the handler:
@ -216,7 +217,7 @@ builder.Services.AddDaprJobsClient();
var app = builder.Build();
//Add our endpoint registration
app.MapDaprScheduledJob("myJob", (IServiceProvider serviceProvider, string? jobName, JobDetails? jobDetails) => {
app.MapDaprScheduledJob("myJob", (IServiceProvider serviceProvider, string jobName, ReadOnlyMemory<byte> jobPayload) => {
var logger = serviceProvider.GetService<ILogger>();
logger?.LogInformation("Received trigger invocation for '{jobName}'", "myJob");
@ -237,13 +238,34 @@ builder.Services.AddDaprJobsClient();
var app = builder.Build();
//Add our endpoint registration
app.MapDaprScheduledJob("myJob", (string? jobName, JobDetails? jobDetails) => {
app.MapDaprScheduledJob("myJob", (string jobName, ReadOnlyMemory<byte> jobPayload) => {
//Do something...
});
app.Run();
```
## Support cancellation tokens when processing mapped invocations
You may want to ensure that timeouts are handled on job invocations so that they don't indefinitely hang and use system resources. When setting up the job mapping, there's an optional `TimeSpan` parameter that can be
provided as the last argument to specify a timeout for the request. Every time the job mapping invocation is triggered, a new `CancellationTokenSource` will be created using this timeout parameter and a `CancellationToken`
will be created from it to put an upper bound on the processing of the request. If a timeout isn't provided, this defaults to `CancellationToken.None` and a timeout will not be automatically applied to the mapping.
```cs
//We have this from the example above
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprJobsClient();
var app = builder.Build();
//Add our endpoint registration
app.MapDaprScheduledJob("myJob", (string jobName, ReadOnlyMemory<byte> jobPayload) => {
//Do something...
}, TimeSpan.FromSeconds(15)); //Assigns a maximum timeout of 15 seconds for handling the invocation request
app.Run();
```
## Register the job
Finally, we have to register the job we want scheduled. Note that from here, all SDK methods have cancellation token support and use a default token if not otherwise set.

View File

@ -165,31 +165,18 @@ var oneWeekFromNow = now.AddDays(7);
await daprJobsClient.ScheduleOneTimeJobWithPayloadAsync("myOtherJob", oneWeekFromNow, "This is a test!");
```
The `JobDetails` type returns the data as a `ReadOnlyMemory<byte>?` so the developer has the freedom to deserialize
The delegate handling the job invocation expects at least two arguments to be present:
- A `string` that is populated with the `jobName`, providing the name of the invoked job
- A `ReadOnlyMemory<byte>` that is populated with the bytes originally provided during the job registration.
Because the payload is stored as a `ReadOnlyMemory<byte>`, the developer has the freedom to serialize and deserialize
as they wish, but there are again two helper extensions included that can deserialize this to either a JSON-compatible
type or a string. Both methods assume that the developer encoded the originally scheduled job (perhaps using the
helper serialization methods) as these methods will not force the bytes to represent something they're not.
To deserialize the bytes to a string, the following helper method can be used:
```cs
if (jobDetails.Payload is not null)
{
string payloadAsString = jobDetails.Payload.DeserializeToString(); //If successful, returns a string value with the value
}
```
To deserialize JSON-encoded UTF-8 bytes to the corresponding type, the following helper method can be used. An
overload argument is available that permits the developer to pass in their own `JsonSerializerOptions` to be applied
during deserialization.
```cs
public sealed record Doodad (string Name, int Value);
//...
if (jobDetails.Payload is not null)
{
var deserializedDoodad = jobDetails.Payload.DeserializeFromJsonBytes<Doodad>();
}
var payloadAsString = Encoding.UTF8.GetString(jobPayload.Span); //If successful, returns a string with the value
```
## Error handling

View File

@ -20,7 +20,7 @@ To load secrets into configuration call the _AddDaprSecretStore_ extension metho
Use Dapr to run the application:
```shell
dapr run --app-id SecretStoreConfigurationProviderSample --components-path ./components/ -- dotnet run
dapr run --app-id SecretStoreConfigurationProviderSample --resources-path ./components/ -- dotnet run
```
### 2. Test the application

View File

@ -147,7 +147,7 @@ cd examples/Client/ConfigurationApi
To run the `ConfigurationExample`, execute the following command:
```bash
dapr run --app-id configexample --components-path ./Components -- dotnet run
dapr run --app-id configexample --resources-path ./Components -- dotnet run
```
### Get Configuration

View File

@ -1,25 +0,0 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: azurekeyvault
spec:
type: crypto.azure.keyvault
metadata:
- name: vaultName
value: "<changeMe>"
- name: azureEnvironment
value: AZUREPUBLICCLOUD
- name: azureTenantId
secretKeyRef:
name: read_azure_tenant_id
key: read_azure_tenant_id
- name: azureClientId
secretKeyRef:
name: read_azure_client_id
key: read_azure_client_id
- name: azureClientSecret
secretKeyRef:
name: read_azure_client_secret
key: read_azure_client_secret
auth:
secureStore: envvar-secret-store

View File

@ -1,7 +0,0 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: envvar-secret-store
spec:
type: secretstores.local.env
version: v1

View File

@ -0,0 +1,11 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localstorage
spec:
type: crypto.dapr.localstorage
version: v1
metadata:
- name: path
# Path is relative to the folder where the example is located
value: ./keys

View File

@ -17,16 +17,13 @@ using Dapr.Client;
namespace Cryptography.Examples
{
internal class EncryptDecryptFileStreamExample : Example
internal class EncryptDecryptFileStreamExample(string componentName, string keyName) : Example
{
public override string DisplayName => "Use Cryptography to encrypt and decrypt a file";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
const string componentName = "azurekeyvault"; // Change this to match the name of the component containing your vault
const string keyName = "myKey";
// The name of the file we're using as an example
const string fileName = "file.txt";
@ -35,21 +32,19 @@ namespace Cryptography.Examples
{
Console.WriteLine(line);
}
Console.WriteLine();
//Encrypt from a file stream and buffer the resulting bytes to an in-memory buffer
await using var encryptFs = new FileStream(fileName, FileMode.Open);
var bufferedEncryptedBytes = new ArrayBufferWriter<byte>();
await foreach (var bytes in (await client.EncryptAsync(componentName, encryptFs, keyName,
new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken))
.WithCancellation(cancellationToken))
await foreach (var bytes in (client.EncryptAsync(componentName, encryptFs, keyName,
new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken)))
{
bufferedEncryptedBytes.Write(bytes.Span);
}
Console.WriteLine($"Encrypted bytes: {Convert.ToBase64String(bufferedEncryptedBytes.GetSpan())}");
Console.WriteLine();
Console.WriteLine("Encrypted bytes:");
Console.WriteLine(Convert.ToBase64String(bufferedEncryptedBytes.WrittenMemory.ToArray()));
//We'll write to a temporary file via a FileStream
var tempDecryptedFile = Path.GetTempFileName();
@ -57,8 +52,8 @@ namespace Cryptography.Examples
//We'll stream the decrypted bytes from a MemoryStream into the above temporary file
await using var encryptedMs = new MemoryStream(bufferedEncryptedBytes.WrittenMemory.ToArray());
await foreach (var result in (await client.DecryptAsync(componentName, encryptedMs, keyName,
cancellationToken)).WithCancellation(cancellationToken))
await foreach (var result in (client.DecryptAsync(componentName, encryptedMs, keyName,
cancellationToken)))
{
decryptFs.Write(result.Span);
}
@ -67,7 +62,7 @@ namespace Cryptography.Examples
//Let's confirm the value as written to the file
var decryptedValue = await File.ReadAllTextAsync(tempDecryptedFile, cancellationToken);
Console.WriteLine($"Decrypted value: ");
Console.WriteLine("Decrypted value: ");
Console.WriteLine(decryptedValue);
//And some cleanup to delete our temp file

View File

@ -17,17 +17,13 @@ using Dapr.Client;
namespace Cryptography.Examples
{
internal class EncryptDecryptStringExample : Example
internal class EncryptDecryptStringExample(string componentName, string keyName) : Example
{
public override string DisplayName => "Using Cryptography to encrypt and decrypt a string";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
const string componentName = "azurekeyvault"; //Change this to match the name of the component containing your vault
const string keyName = "myKey"; //Change this to match the name of the key in your Vault
const string plaintextStr = "This is the value we're going to encrypt today";
Console.WriteLine($"Original string value: '{plaintextStr}'");
@ -40,7 +36,7 @@ namespace Cryptography.Examples
Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'");
//Decrypt the string
var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, new DecryptionOptions(), cancellationToken);
var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, cancellationToken);
Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'");
}
}

View File

@ -17,10 +17,13 @@ namespace Cryptography
{
class Program
{
private const string ComponentName = "localstorage";
private const string KeyName = "rsa-private-key.pem"; //This should match the name of your generated key - this sample expects an RSA symmetrical key.
private static readonly Example[] Examples = new Example[]
{
new EncryptDecryptStringExample(),
new EncryptDecryptFileStreamExample()
new EncryptDecryptStringExample(ComponentName, KeyName),
new EncryptDecryptFileStreamExample(ComponentName, KeyName)
};
static async Task<int> Main(string[] args)
@ -34,7 +37,7 @@ namespace Cryptography
return 0;
}
Console.WriteLine("Hello, please choose a sample to run:");
Console.WriteLine("Hello, please choose a sample to run by passing your selection's number into the arguments, e.g. 'dotnet run 0':");
for (var i = 0; i < Examples.Length; i++)
{
Console.WriteLine($"{i}: {Examples[i].DisplayName}");

View File

@ -50,6 +50,21 @@ button. Ensuring that the "User, group or service principal" option is selected,
Add to add this service principal to the list of members for the new role assignment and click Review + Assign twice to assign the role. This will take effect within a few seconds
or minutes. This step ensures that while Dapr can authenticate as your service principal, that it also has permission to access and use the key in your Key Vault.
## Generating the Keys
This sample requires a private RSA key to be generated and placed in the `/keys` directory within the project.
If you have OpenSSL installed on your machine, you can generate the key by navigating first
into the project directory and then running the following command:
```bash
# Generates a private RSA 40960-bit key named 'rsa-private-key.pem'
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
```
> **WARNING: This RSA key is included in this project strictly for demonstration and testing purposes.**
> - Do **NOT** use this key in any production environment or for any real-world applications.
> - This key is publicly available and should be considered compromised.
> - Generating and using your own secure keys is essential for maintaining security in your projects.
## Running the example
To run the sample locally, run this command in the DaprClient directory:

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC0URLpxZCqDv7S
WfROh2Kei4VCEayNu/TK3NaD/QlIpip1rrsPKgTfTOZoRmkmG0Qj59srEJi2GEhL
xpjvRQpA/C/OS+KELU8AeGrqHw7uN/a99NkoAr+zYDCyY9yckPeC5wGxc0/Q6HQT
mWp+YcpR9wFO0PmTVlObssibagjjRNX7z/ZosecOOqjnAqlnYoHMavvoCD5fxM7y
cm7so0JWooXwVaZKgehBEBg1W5F0q5e9ssAQk3lY6IUd5sOskiylTNf/+3r1JU0j
YM8ik3a1/dyDALVXpLSfz7FM9VEj4QjiPF4UuXeBHPDFFiKWbiKfbjqvZ2Sz7Gl7
c5rTk1Fozpr70E/wihrrv22Mxs0sEPdtemQgHXroQfRW8K4FhI0WHs7tR2gVxLHu
OAU9LzCngz4yITh1eixVDmm/B5ZtNVrTQmaY84vGqhrFp+asyFNiXbhUAcT7D/q6
w/c4aQ635ntCFSPYpWvhKqrqVDsoanD/5AWfc3+6Ek2/GVMyEQq+9tnCMM10EVSX
8PsoAWHESDFude5zkHzn7IKy8mh6lfheEbBI5zN9z7WGexyiBgljmyUHXx6Pd8Uc
yxpLRm94kynkDXD9SapQLzXmz+D+X/OYeADMIDWlbdXiIb1+2Q62H1lo6n10KVP7
oEr8BHvcMFY89kwK4lKscUupn8xkzwIDAQABAoICACDuu78Rc8Hzeivt/PZIuMTP
I5f1BWhffy571fwGP2dS3edfcc+rs3cbIuvBjFvG2BOcuYUsg0+isLWSQIVWvTAw
PwT1DBpq8gZad+Bpqr7sXrbD3NN3aQ64TzyNi5HW0jXIviDsOBQmGGkp+G67qol8
zPLZrPNxbVS++u+Tlqr3fAOBMHZfo50QLp/+dvUoYx90HKz8sHOqTMewCb1Tdf6/
sSm7YuMxxbr4VwuLvU2rN0wQtQ5x+NQ5p3JWHr/KdLf+CGc6xXK3jNaczEf62dAU
XO1aOESZEtorQy0Ukuy0IXy8XMx5MS/WGs1MJSYHWHB43+QARL6tu3guHYVt3wyv
W6YTglQsSKc6uuK4JTZOx1VYZjjnSdeY/xiUmZGYp4ZiC9p8b9NvXmZT2EwqhCVt
4OTcX4lkwGAsKcoEdLHi0K5CbBfYJsRgVVheDjP0xUFjCJCYqfqo2rE5YMXMTeY7
clYEOXKGxwuy1Iu8nKqtWAV5r/eSmXBdxBqEBW9oxJfnnwNPG+yOk0Qkd1vaRj00
mdKCOjgB2fOuPX2JRZ2z41Cem3gqhH0NQGrx3APV4egGrYAMClasgtZkUeUOIgK5
xLlC/6svuHNyKXAKFpOubEy1FM8jz7111eNHxHRDP3+vH3u4CfAD2Sl+VDZdg51i
WmVpT+B/DrnlHVSP2/XNAoIBAQD7F49oSdveKuO/lAyqkE9iF61i09G0b0ouDGUI
qx+pd5/8vUcqi4upCxz+3AqMPWZRIqOyo8EUP7f4rSJrXn8U2SwnFfi4k2jiqmEA
Wr0b8z5P1q5MH6BtVDa0Sr1R8xI9s3UgIs4pUKgBoQu9+U4Du4NSucQFcea8nIVY
lLCqQcRhz8bCJPCNuHay5c77kK3Te197KPMasNurTNMOJcPMG95CZLB8Clf4A+pw
fixvA1/fE4mFo1L7Ymxoz5lFYVWOTY9hh50Kqz57wxw4laU4ii+MaJj+YHuNR83N
cO6FztUYKMR8BPgtl3/POTHTofSg7eIOiUYwcfRr6jbMWlsDAoIBAQC311xiMpho
Hvdcvp3/urrIp2QhdD05n6TnZOPkpnd9kwGku2RA+occDQOg/BzADVwJaR/aE97F
jbfRlfBesTZlUec0EwjKIFbeYh+QS/RmjQe9zpPQWMo1M7y0fMWU+yXRUcNBpcuy
R6KlphK0k4xFkIAdC3QHmJQ0XvOpqvrhFy3i/Prc5Wlg29FYBBTAF0WZCZ4uCG34
D0eG0CNaf8w9g9ClbU6nGLBCMcgjEOPYfyrJaedM+jXennLDPG6ySytrGwnwLAQc
Okx+SrIiNHUpQGKteT88Kdpgo3F4KUX/pm84uGdxrOpDS7L0T9/G4CbjzCe1nHeS
fJJsw5JN+Z9FAoIBAGn5S6FsasudtnnI9n+WYKq564fmdn986QX+XTYHY1mXD4MQ
L9UZCFzUP+yg2iLOVzyvLf/bdUYijnb6O6itPV2DO0tTzqG4NXBVEJOhuGbvhsET
joS6ZG9AN8ZoNPc9a9l2wFxL1E9Dp2Ton5gSfIa+wXJMzRqvM/8u4Gi+eMGi+Et/
8hdGl/B4hkCDFZS/P14el/HXGqONOWlXB0zVS4n9yRSkgogXpYEbxfqshfxkpDX2
fPhWMlO++ppR5BKQPhfNTFKRdgpms/xwIJ0RK6ZtTBwqmUfjWMIMKCQpIcJ/xRhp
PGRLhKNZaawAK7Nyi1jQjbQs497WeZ6CP5aIHBkCggEALHyl83FQ5ilQLJZH/6E9
H9854MqTIkWajxAgAa2yzqVrSWS7XuoBFe2kSimX/3V8Jx7UQV57kwy3RbVl5FQ3
2I7YRwawItFulAPkpXNr4gEQtYKuzEUgMX2ilX54BZQ804lYmaM4Rp0FI9arQh1O
XWsZRW4HFut6Oa4cgptIeH22ce5L+nZdaL3oy8a5Cr7W7bChIXySt+tioKHvXC/+
yYgDTnTECrVzuaD4UFv+9t3XCcRh34PQ010+YjZWhzifehyh7AeKuxX0er8ymgpd
q6zT9CyZ+8IZATer9qruMG4jDfO5vI1eZwiDdpF5klOdtZQqq80ANmeEu2McHVhh
jQKCAQBbohPxMb3QYdukGp8IsIF04GfnTgaDbRgl4KeUyzdBN3nzvCKK0HDluptR
4Ua64JksGG24gsTBy6yuQoGRCG0LJe0Ty3TRRnvZ8MpADoNMObspMSC8n8kk6ps+
SoG1U9t6HYlIgQagvTc7mTmCmwYX1zlCoZp24yz5pDkKxqoPFDtrGlXxeUgOhpDT
Mzi+DNTz9sH9vod4ibQiOseUxITwQpXHTJVrtNfvva6xjlhq+GGCuKIUwkUKOvBC
ds7SR9demn69aWCyzXqD1cTnmxtn6bNPukwowg7a07ieUyKftcJ1icOWQ/bdQkEf
dV1dhNiQEnqs4vDBVn40dnTKSSG2
-----END PRIVATE KEY-----

View File

@ -24,7 +24,7 @@ cd examples/Client/DistributedLock
In order to run the application that generates data for the workers to process, simply run the following command:
```bash
dapr run --components-path ./Components --app-id generator -- dotnet run
dapr run --resources-path ./Components --app-id generator -- dotnet run
```
This application will create a new file to process once every 10 seconds. The files are stored in `DistributedLock/tmp`.
@ -33,8 +33,8 @@ This application will create a new file to process once every 10 seconds. The fi
In order to properly demonstrate locking, this application will be run more than once with the same App ID. However, the applications do need different ports in order to properly receive bindings. Run them with the command below:
```bash
dapr run --components-path ./Components --app-id worker --app-port 5000 -- dotnet run
dapr run --components-path ./Components --app-id worker --app-port 5001 -- dotnet run
dapr run --resources-path ./Components --app-id worker --app-port 5000 -- dotnet run
dapr run --resources-path ./Components --app-id worker --app-port 5001 -- dotnet run
```
After running the applications, they will attempt to process files. You should see output such as:

View File

@ -16,43 +16,36 @@ using System.Text;
using Dapr.Jobs;
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
using Dapr.Jobs.Models.Responses;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprJobsClient();
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
var app = builder.Build();
//Set a handler to deal with incoming jobs
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
app.MapDaprScheduledJobHandler((string? jobName, DaprJobDetails? jobDetails, ILogger? logger, CancellationToken cancellationToken) =>
app.MapDaprScheduledJobHandler(async (string jobName, ReadOnlyMemory<byte> jobPayload, ILogger? logger, CancellationToken cancellationToken) =>
{
logger?.LogInformation("Received trigger invocation for job '{jobName}'", jobName);
if (jobDetails?.Payload is not null)
{
var deserializedPayload = Encoding.UTF8.GetString(jobDetails.Payload);
logger?.LogInformation("Received invocation for the job '{jobName}' with payload '{deserializedPayload}'",
jobName, deserializedPayload);
//Do something that needs the cancellation token
}
else
{
logger?.LogWarning("Failed to deserialize payload for job '{jobName}'", jobName);
}
var deserializedPayload = Encoding.UTF8.GetString(jobPayload.Span);
logger?.LogInformation("Received invocation for the job '{jobName}' with payload '{deserializedPayload}'",
jobName, deserializedPayload);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
return Task.CompletedTask;
}, cancellationTokenSource.Token);
}, TimeSpan.FromSeconds(5));
app.Run();
await using var scope = app.Services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger>();
using var scope = app.Services.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
var daprJobsClient = scope.ServiceProvider.GetRequiredService<DaprJobsClient>();
logger.LogInformation("Scheduling one-time job 'myJob' to execute 10 seconds from now");
await daprJobsClient.ScheduleJobAsync("myJob", DaprJobSchedule.FromDateTime(DateTime.UtcNow.AddSeconds(10)),
Encoding.UTF8.GetBytes("This is a test"));
await daprJobsClient.ScheduleJobAsync("myJob", DaprJobSchedule.FromDuration(TimeSpan.FromSeconds(2)),
Encoding.UTF8.GetBytes("This is a test"), repeats: 10);
logger.LogInformation("Scheduled one-time job 'myJob'");
app.Run();
#pragma warning restore CS0618 // Type or member is obsolete

View File

@ -77,7 +77,11 @@ public sealed class DaprConversationClient : DaprAIClient
if (options is not null)
{
request.ContextID = options.ConversationId;
if (options.ConversationId is not null)
{
request.ContextID = options.ConversationId;
}
request.ScrubPII = options.ScrubPII;
foreach (var (key, value) in options.Metadata)
@ -96,7 +100,7 @@ public sealed class DaprConversationClient : DaprAIClient
request.Inputs.Add(new P.ConversationInput
{
ScrubPII = input.ScrubPII,
Message = input.Message,
Content = input.Content,
Role = input.Role.GetValueFromEnumMember()
});
}

View File

@ -16,7 +16,7 @@ namespace Dapr.AI.Conversation;
/// <summary>
/// Represents an input for the Dapr Conversational API.
/// </summary>
/// <param name="Message">The message to send to the LLM.</param>
/// <param name="Content">The content to send to the LLM.</param>
/// <param name="Role">The role indicating the entity providing the message.</param>
/// <param name="ScrubPII">If true, scrubs the data that goes into the LLM.</param>
public sealed record DaprConversationInput(string Message, DaprConversationRole Role, bool ScrubPII = false);
public sealed record DaprConversationInput(string Content, DaprConversationRole Role, bool ScrubPII = false);

View File

@ -265,7 +265,7 @@ namespace Dapr.Actors
return this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
}
public async Task<Stream> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default)
public async Task<HttpResponseMessage> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default)
{
var relativeUrl = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName);
@ -278,8 +278,7 @@ namespace Dapr.Actors
return request;
}
var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
return await response.Content.ReadAsStreamAsync();
return await this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
}
public Task UnregisterReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default)

View File

@ -0,0 +1,123 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace Dapr.Actors.Extensions;
internal static class DurationExtensions
{
/// <summary>
/// Used to parse the duration string accompanying an @every expression.
/// </summary>
private static readonly Regex durationRegex = new(@"(?<value>\d+(\.\d+)?)(?<unit>ns|us|µs|ms|s|m|h)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// A regular expression used to evaluate whether a given prefix period embodies an @every statement.
/// </summary>
private static readonly Regex isEveryExpression = new(@"^@every (\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$");
/// <summary>
/// The various acceptable duration values for a period expression.
/// </summary>
private static readonly string[] acceptablePeriodValues =
{
"yearly", "monthly", "weekly", "daily", "midnight", "hourly"
};
private const string YearlyPrefixPeriod = "@yearly";
private const string MonthlyPrefixPeriod = "@monthly";
private const string WeeklyPrefixPeriod = "@weekly";
private const string DailyPrefixPeriod = "@daily";
private const string MidnightPrefixPeriod = "@midnight";
private const string HourlyPrefixPeriod = "@hourly";
private const string EveryPrefixPeriod = "@every";
/// <summary>
/// Indicates that the schedule represents a prefixed period expression.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
public static bool IsDurationExpression(this string expression) => expression.StartsWith('@') &&
(isEveryExpression.IsMatch(expression) ||
expression.EndsWithAny(acceptablePeriodValues, StringComparison.InvariantCulture));
/// <summary>
/// Creates a TimeSpan value from the prefixed period value.
/// </summary>
/// <param name="period">The prefixed period value to parse.</param>
/// <returns>A TimeSpan value matching the provided period.</returns>
public static TimeSpan FromPrefixedPeriod(this string period)
{
if (period.StartsWith(YearlyPrefixPeriod))
{
var dateTime = DateTime.UtcNow;
return dateTime.AddYears(1) - dateTime;
}
if (period.StartsWith(MonthlyPrefixPeriod))
{
return TimeSpan.FromDays(30);
}
if (period.StartsWith(MidnightPrefixPeriod))
{
return TimeSpan.Zero;
}
if (period.StartsWith(WeeklyPrefixPeriod))
{
return TimeSpan.FromDays(7);
}
if (period.StartsWith(DailyPrefixPeriod) || period.StartsWith(MidnightPrefixPeriod))
{
return TimeSpan.FromDays(1);
}
if (period.StartsWith(HourlyPrefixPeriod))
{
return TimeSpan.FromHours(1);
}
if (period.StartsWith(EveryPrefixPeriod))
{
//A sequence of decimal numbers each with an optional fraction and unit suffix
//Valid time units are: 'ns', 'us'/'µs', 'ms', 's', 'm', and 'h'
double totalMilliseconds = 0;
var durationString = period.Split(' ').Last().Trim();
foreach (Match match in durationRegex.Matches(durationString))
{
var value = double.Parse(match.Groups["value"].Value, CultureInfo.InvariantCulture);
var unit = match.Groups["unit"].Value.ToLower();
totalMilliseconds += unit switch
{
"ns" => value / 1_000_000,
"us" or "µs" => value / 1_000,
"ms" => value,
"s" => value * 1_000,
"m" => value * 1_000 * 60,
"h" => value * 1_000 * 60 * 60,
_ => throw new ArgumentException($"Unknown duration unit: {unit}")
};
}
return TimeSpan.FromMilliseconds(totalMilliseconds);
}
throw new ArgumentException($"Unknown prefix period expression: {period}");
}
}

View File

@ -0,0 +1,32 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Dapr.Actors.Extensions;
internal static class StringExtensions
{
/// <summary>
/// Extension method that validates a string against a list of possible matches.
/// </summary>
/// <param name="value">The string value to evaluate.</param>
/// <param name="possibleValues">The possible values to look for a match within.</param>
/// <param name="comparisonType">The type of string comparison to perform.</param>
/// <returns>True if the value ends with any of the possible values; otherwise false.</returns>
public static bool EndsWithAny(this string value, IReadOnlyList<string> possibleValues, StringComparison comparisonType )
=> possibleValues.Any(val => value.EndsWith(val, comparisonType));
}

View File

@ -11,6 +11,8 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Net.Http;
namespace Dapr.Actors
{
using System.IO;
@ -81,8 +83,8 @@ namespace Dapr.Actors
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to unregister.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
Task<Stream> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default);
/// <returns>A <see cref="Task"/> containing the response of the asynchronous HTTP operation.</returns>
Task<HttpResponseMessage> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default);
/// <summary>
/// Unregisters a reminder.

View File

@ -223,7 +223,7 @@ namespace Dapr.Actors.Runtime
internal async Task FireTimerAsync(ActorId actorId, Stream requestBodyStream, CancellationToken cancellationToken = default)
{
#pragma warning disable 0618
var timerData = await JsonSerializer.DeserializeAsync<TimerInfo>(requestBodyStream);
var timerData = await DeserializeAsync(requestBodyStream);
#pragma warning restore 0618
// Create a Func to be invoked by common method.
@ -243,6 +243,62 @@ namespace Dapr.Actors.Runtime
await this.DispatchInternalAsync(actorId, this.timerMethodContext, RequestFunc, cancellationToken);
}
#pragma warning disable 0618
internal static async Task<TimerInfo> DeserializeAsync(Stream stream)
{
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
if (json.ValueKind == JsonValueKind.Null)
{
return null;
}
var setAnyProperties = false; // Used to determine if anything was actually deserialized
var dueTime = TimeSpan.Zero;
var callback = "";
var period = TimeSpan.Zero;
var data = Array.Empty<byte>();
TimeSpan? ttl = null;
if (json.TryGetProperty("callback", out var callbackProperty))
{
setAnyProperties = true;
callback = callbackProperty.GetString();
}
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
{
setAnyProperties = true;
var dueTimeString = dueTimeProperty.GetString();
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
}
if (json.TryGetProperty("period", out var periodProperty))
{
setAnyProperties = true;
var periodString = periodProperty.GetString();
(period, _) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
}
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
{
setAnyProperties = true;
data = dataProperty.GetBytesFromBase64();
}
if (json.TryGetProperty("ttl", out var ttlProperty))
{
setAnyProperties = true;
var ttlString = ttlProperty.GetString();
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
}
if (!setAnyProperties)
{
return null; //No properties were ever deserialized, so return null instead of default values
}
return new TimerInfo(callback, data, dueTime, period, ttl);
}
#pragma warning restore 0618
internal async Task ActivateActorAsync(ActorId actorId)
{
// An actor is activated by "Dapr" runtime when a call is to be made for an actor.

View File

@ -11,138 +11,148 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Runtime
using Dapr.Actors.Extensions;
namespace Dapr.Actors.Runtime;
using System;
using System.Text;
using System.Text.RegularExpressions;
internal static class ConverterUtils
{
using System;
using System.Text;
using System.Text.RegularExpressions;
internal class ConverterUtils
private static Regex regex = new("^(R(?<repetition>\\d+)/)?P((?<year>\\d+)Y)?((?<month>\\d+)M)?((?<week>\\d+)W)?((?<day>\\d+)D)?(T((?<hour>\\d+)H)?((?<minute>\\d+)M)?((?<second>\\d+)S)?)?$", RegexOptions.Compiled);
public static TimeSpan ConvertTimeSpanFromDaprFormat(string valueString)
{
private static Regex regex = new Regex("^(R(?<repetition>\\d+)/)?P((?<year>\\d+)Y)?((?<month>\\d+)M)?((?<week>\\d+)W)?((?<day>\\d+)D)?(T((?<hour>\\d+)H)?((?<minute>\\d+)M)?((?<second>\\d+)S)?)?$", RegexOptions.Compiled);
public static TimeSpan ConvertTimeSpanFromDaprFormat(string valueString)
if (string.IsNullOrEmpty(valueString))
{
if (string.IsNullOrEmpty(valueString))
{
var never = TimeSpan.FromMilliseconds(-1);
return never;
}
// TimeSpan is a string. Format returned by Dapr is: 1h4m5s4ms4us4ns
// acceptable values are: m, s, ms, us(micro), ns
var spanOfValue = valueString.AsSpan();
// Change the value returned by Dapr runtime, so that it can be parsed with TimeSpan.
// Format returned by Dapr runtime: 4h15m50s60ms. It doesnt have days.
// Dapr runtime should handle timespans in ISO 8601 format.
// Replace ms before m & s. Also append 0 days for parsing correctly with TimeSpan
int hIndex = spanOfValue.IndexOf('h');
int mIndex = spanOfValue.IndexOf('m');
int sIndex = spanOfValue.IndexOf('s');
int msIndex = spanOfValue.IndexOf("ms");
// handle days from hours.
var hoursSpan = spanOfValue.Slice(0, hIndex);
var hours = int.Parse(hoursSpan);
var days = hours / 24;
hours %= 24;
var minutesSpan = spanOfValue[(hIndex + 1)..mIndex];
var minutes = int.Parse(minutesSpan);
var secondsSpan = spanOfValue[(mIndex + 1)..sIndex];
var seconds = int.Parse(secondsSpan);
var millisecondsSpan = spanOfValue[(sIndex + 1)..msIndex];
var milliseconds = int.Parse(millisecondsSpan);
return new TimeSpan(days, hours, minutes, seconds, milliseconds);
var never = TimeSpan.FromMilliseconds(-1);
return never;
}
if (valueString.IsDurationExpression())
{
return valueString.FromPrefixedPeriod();
}
public static string ConvertTimeSpanValueInDaprFormat(TimeSpan? value)
{
// write in format expected by Dapr, it only accepts h, m, s, ms, us(micro), ns
var stringValue = string.Empty;
if (value.Value >= TimeSpan.Zero)
{
var hours = (value.Value.Days * 24) + value.Value.Hours;
stringValue = FormattableString.Invariant($"{hours}h{value.Value.Minutes}m{value.Value.Seconds}s{value.Value.Milliseconds}ms");
}
// TimeSpan is a string. Format returned by Dapr is: 1h4m5s4ms4us4ns
// acceptable values are: m, s, ms, us(micro), ns
var spanOfValue = valueString.AsSpan();
// Change the value returned by Dapr runtime, so that it can be parsed with TimeSpan.
// Format returned by Dapr runtime: 4h15m50s60ms. It doesnt have days.
// Dapr runtime should handle timespans in ISO 8601 format.
// Replace ms before m & s. Also append 0 days for parsing correctly with TimeSpan
int hIndex = spanOfValue.IndexOf('h');
int mIndex = spanOfValue.IndexOf('m');
int sIndex = spanOfValue.IndexOf('s');
int msIndex = spanOfValue.IndexOf("ms");
// handle days from hours.
var hoursSpan = spanOfValue[..hIndex];
var hours = int.Parse(hoursSpan);
var days = hours / 24;
hours %= 24;
var minutesSpan = spanOfValue[(hIndex + 1)..mIndex];
var minutes = int.Parse(minutesSpan);
var secondsSpan = spanOfValue[(mIndex + 1)..sIndex];
var seconds = int.Parse(secondsSpan);
var millisecondsSpan = spanOfValue[(sIndex + 1)..msIndex];
var milliseconds = int.Parse(millisecondsSpan);
return new TimeSpan(days, hours, minutes, seconds, milliseconds);
}
public static string ConvertTimeSpanValueInDaprFormat(TimeSpan? value)
{
// write in format expected by Dapr, it only accepts h, m, s, ms, us(micro), ns
var stringValue = string.Empty;
if (value is null)
return stringValue;
}
public static string ConvertTimeSpanValueInISO8601Format(TimeSpan value, int? repetitions)
if (value.Value >= TimeSpan.Zero)
{
StringBuilder builder = new StringBuilder();
if (repetitions == null)
{
return ConvertTimeSpanValueInDaprFormat(value);
}
if (value.Milliseconds > 0)
{
throw new ArgumentException("The TimeSpan value, combined with repetition cannot be in milliseconds.", nameof(value));
}
builder.AppendFormat("R{0}/P", repetitions);
if(value.Days > 0)
{
builder.AppendFormat("{0}D", value.Days);
}
builder.Append("T");
if(value.Hours > 0)
{
builder.AppendFormat("{0}H", value.Hours);
}
if(value.Minutes > 0)
{
builder.AppendFormat("{0}M", value.Minutes);
}
if(value.Seconds > 0)
{
builder.AppendFormat("{0}S", value.Seconds);
}
return builder.ToString();
var hours = (value.Value.Days * 24) + value.Value.Hours;
stringValue = FormattableString.Invariant($"{hours}h{value.Value.Minutes}m{value.Value.Seconds}s{value.Value.Milliseconds}ms");
}
return stringValue;
}
public static (TimeSpan, int?) ConvertTimeSpanValueFromISO8601Format(string valueString)
public static string ConvertTimeSpanValueInISO8601Format(TimeSpan value, int? repetitions)
{
StringBuilder builder = new StringBuilder();
if (repetitions == null)
{
// ISO 8601 format can be Rn/PaYbMcHTdHeMfS or PaYbMcHTdHeMfS so if it does
// not start with R or P then assuming it to default Dapr format without repetition
if (!(valueString.StartsWith('R') || valueString.StartsWith('P')))
{
return (ConvertTimeSpanFromDaprFormat(valueString), -1);
}
var matches = regex.Match(valueString);
var repetition = matches.Groups["repetition"].Success ? int.Parse(matches.Groups["repetition"].Value) : (int?)null;
var days = 0;
var year = matches.Groups["year"].Success ? int.Parse(matches.Groups["year"].Value) : 0;
days = year * 365;
var month = matches.Groups["month"].Success ? int.Parse(matches.Groups["month"].Value) : 0;
days += month * 30;
var week = matches.Groups["week"].Success ? int.Parse(matches.Groups["week"].Value) : 0;
days += week * 7;
var day = matches.Groups["day"].Success ? int.Parse(matches.Groups["day"].Value) : 0;
days += day;
var hour = matches.Groups["hour"].Success ? int.Parse(matches.Groups["hour"].Value) : 0;
var minute = matches.Groups["minute"].Success ? int.Parse(matches.Groups["minute"].Value) : 0;
var second = matches.Groups["second"].Success ? int.Parse(matches.Groups["second"].Value) : 0;
return (new TimeSpan(days, hour, minute, second), repetition);
return ConvertTimeSpanValueInDaprFormat(value);
}
if (value.Milliseconds > 0)
{
throw new ArgumentException("The TimeSpan value, combined with repetition cannot be in milliseconds.", nameof(value));
}
builder.Append($"R{repetitions}/P");
if(value.Days > 0)
{
builder.Append($"{value.Days}D");
}
builder.Append('T');
if(value.Hours > 0)
{
builder.Append($"{value.Hours}H");
}
if(value.Minutes > 0)
{
builder.Append($"{value.Minutes}M");
}
if(value.Seconds > 0)
{
builder.Append($"{value.Seconds}S");
}
return builder.ToString();
}
public static (TimeSpan, int?) ConvertTimeSpanValueFromISO8601Format(string valueString)
{
// ISO 8601 format can be Rn/PaYbMcHTdHeMfS or PaYbMcHTdHeMfS so if it does
// not start with R or P then assuming it to default Dapr format without repetition
if (!(valueString.StartsWith('R') || valueString.StartsWith('P')))
{
return (ConvertTimeSpanFromDaprFormat(valueString), -1);
}
var matches = regex.Match(valueString);
var repetition = matches.Groups["repetition"].Success ? int.Parse(matches.Groups["repetition"].Value) : (int?)null;
var days = 0;
var year = matches.Groups["year"].Success ? int.Parse(matches.Groups["year"].Value) : 0;
days = year * 365;
var month = matches.Groups["month"].Success ? int.Parse(matches.Groups["month"].Value) : 0;
days += month * 30;
var week = matches.Groups["week"].Success ? int.Parse(matches.Groups["week"].Value) : 0;
days += week * 7;
var day = matches.Groups["day"].Success ? int.Parse(matches.Groups["day"].Value) : 0;
days += day;
var hour = matches.Groups["hour"].Success ? int.Parse(matches.Groups["hour"].Value) : 0;
var minute = matches.Groups["minute"].Success ? int.Parse(matches.Groups["minute"].Value) : 0;
var second = matches.Groups["second"].Success ? int.Parse(matches.Groups["second"].Value) : 0;
return (new TimeSpan(days, hour, minute, second), repetition);
}
}

View File

@ -15,6 +15,7 @@ using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using Grpc.Core;
namespace Dapr.Actors.Runtime
{
@ -45,9 +46,14 @@ namespace Dapr.Actors.Runtime
throw new ArgumentNullException(nameof(token));
}
var responseStream = await this.interactor.GetReminderAsync(token.ActorType, token.ActorId.ToString(), token.Name);
var reminder = await DeserializeReminderAsync(responseStream, token);
return reminder;
var response = await this.interactor.GetReminderAsync(token.ActorType, token.ActorId.ToString(), token.Name);
if ((int)response.StatusCode == 500)
{
return null;
}
var responseStream = await response.Content.ReadAsStreamAsync();
return await DeserializeReminderAsync(responseStream, token);
}
public override async Task UnregisterReminderAsync(ActorReminderToken reminder)
@ -84,24 +90,26 @@ namespace Dapr.Actors.Runtime
await this.interactor.UnregisterTimerAsync(timer.ActorType, timer.ActorId.ToString(), timer.Name);
}
private async ValueTask<string> SerializeReminderAsync(ActorReminder reminder)
private static async ValueTask<string> SerializeReminderAsync(ActorReminder reminder)
{
var info = new ReminderInfo(reminder.State, reminder.DueTime, reminder.Period, reminder.Repetitions,
reminder.Ttl);
return await info.SerializeAsync();
}
private async ValueTask<ActorReminder> DeserializeReminderAsync(Stream stream, ActorReminderToken token)
private static async ValueTask<ActorReminder> DeserializeReminderAsync(Stream stream, ActorReminderToken token)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
var info = await ReminderInfo.DeserializeAsync(stream);
if(info == null)
if (info == null)
{
return null;
}
var reminder = new ActorReminder(token.ActorType, token.ActorId, token.Name, info.Data, info.DueTime,
info.Period);
return reminder;

View File

@ -11,99 +11,110 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Runtime
#nullable enable
namespace Dapr.Actors.Runtime;
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// represents the wire format used by Dapr to store reminder info with the runtime
internal class ReminderInfo
{
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// represents the wire format used by Dapr to store reminder info with the runtime
internal class ReminderInfo
public ReminderInfo(
byte[] data,
TimeSpan dueTime,
TimeSpan period,
int? repetitions = null,
TimeSpan? ttl = null)
{
public ReminderInfo(
byte[] data,
TimeSpan dueTime,
TimeSpan period,
int? repetitions = null,
TimeSpan? ttl = null)
{
this.Data = data;
this.DueTime = dueTime;
this.Period = period;
this.Ttl = ttl;
this.Repetitions = repetitions;
}
this.Data = data;
this.DueTime = dueTime;
this.Period = period;
this.Ttl = ttl;
this.Repetitions = repetitions;
}
public TimeSpan DueTime { get; private set; }
public TimeSpan DueTime { get; private set; }
public TimeSpan Period { get; private set; }
public TimeSpan Period { get; private set; }
public byte[] Data { get; private set; }
public byte[] Data { get; private set; }
public TimeSpan? Ttl { get; private set; }
public TimeSpan? Ttl { get; private set; }
public int? Repetitions { get; private set; }
public int? Repetitions { get; private set; }
internal static async Task<ReminderInfo> DeserializeAsync(Stream stream)
internal static async Task<ReminderInfo?> DeserializeAsync(Stream stream)
{
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
if(json.ValueKind == JsonValueKind.Null)
{
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
if(json.ValueKind == JsonValueKind.Null)
{
return null;
}
var dueTime = default(TimeSpan);
var period = default(TimeSpan);
var data = default(byte[]);
int? repetition = null;
TimeSpan? ttl = null;
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
{
var dueTimeString = dueTimeProperty.GetString();
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
}
if (json.TryGetProperty("period", out var periodProperty))
{
var periodString = periodProperty.GetString();
(period, repetition) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
}
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
{
data = dataProperty.GetBytesFromBase64();
}
if (json.TryGetProperty("ttl", out var ttlProperty))
{
var ttlString = ttlProperty.GetString();
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
}
return new ReminderInfo(data, dueTime, period, repetition, ttl);
return null;
}
internal async ValueTask<string> SerializeAsync()
var setAnyProperties = false; //Used to determine if anything was actually deserialized
var dueTime = TimeSpan.Zero;
var period = TimeSpan.Zero;
var data = Array.Empty<byte>();
int? repetition = null;
TimeSpan? ttl = null;
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
{
using var stream = new MemoryStream();
using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
writer.WriteStartObject();
writer.WriteString("dueTime", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.DueTime));
writer.WriteString("period", ConverterUtils.ConvertTimeSpanValueInISO8601Format(
this.Period, this.Repetitions));
writer.WriteBase64String("data", this.Data);
if (Ttl != null)
{
writer.WriteString("ttl", ConverterUtils.ConvertTimeSpanValueInDaprFormat(Ttl));
}
writer.WriteEndObject();
await writer.FlushAsync();
return Encoding.UTF8.GetString(stream.ToArray());
setAnyProperties = true;
var dueTimeString = dueTimeProperty.GetString();
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
}
if (json.TryGetProperty("period", out var periodProperty))
{
setAnyProperties = true;
var periodString = periodProperty.GetString();
(period, repetition) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
}
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
{
setAnyProperties = true;
data = dataProperty.GetBytesFromBase64();
}
if (json.TryGetProperty("ttl", out var ttlProperty))
{
setAnyProperties = true;
var ttlString = ttlProperty.GetString();
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
}
if (!setAnyProperties)
{
return null; //No properties were ever deserialized, so return null instead of default values
}
return new ReminderInfo(data, dueTime, period, repetition, ttl);
}
internal async ValueTask<string> SerializeAsync()
{
using var stream = new MemoryStream();
await using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
writer.WriteStartObject();
writer.WriteString("dueTime", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.DueTime));
writer.WriteString("period", ConverterUtils.ConvertTimeSpanValueInISO8601Format(
this.Period, this.Repetitions));
writer.WriteBase64String("data", this.Data);
if (Ttl != null)
{
writer.WriteString("ttl", ConverterUtils.ConvertTimeSpanValueInDaprFormat(Ttl));
}
writer.WriteEndObject();
await writer.FlushAsync();
return Encoding.UTF8.GetString(stream.ToArray());
}
}

View File

@ -0,0 +1,153 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Dapr.Client.Crypto;
/// <summary>
/// Provides the implementation to decrypt a stream of plaintext data with the Dapr runtime.
/// </summary>
internal sealed class DecryptionStreamProcessor : IDisposable
{
private bool disposed;
private readonly Channel<ReadOnlyMemory<byte>> outputChannel = Channel.CreateUnbounded<ReadOnlyMemory<byte>>();
/// <summary>
/// Surfaces any exceptions encountered while asynchronously processing the inbound and outbound streams.
/// </summary>
internal event EventHandler<Exception>? OnException;
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to decrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="options">The decryption options.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
public async Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> call,
int streamingBlockSizeInBytes,
Autogenerated.DecryptRequestOptions options,
CancellationToken cancellationToken)
{
//Read from the input stream and write to the gRPC call
_ = Task.Run(async () =>
{
try
{
await using var bufferedStream = new BufferedStream(inputStream, streamingBlockSizeInBytes);
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead = await bufferedStream.ReadAsync(buffer, cancellationToken)) > 0)
{
var request = new Autogenerated.DecryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
};
//Only include the options in the first message
if (sequenceNumber == 0)
{
request.Options = options;
}
await call.RequestStream.WriteAsync(request, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Expected cancellation exception
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
await call.RequestStream.CompleteAsync();
}
}, cancellationToken);
//Start reading from the gRPC call and writing to the output channel
_ = Task.Run(async () =>
{
try
{
await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
{
await outputChannel.Writer.WriteAsync(response.Payload.Data.Memory, cancellationToken);
}
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
outputChannel.Writer.Complete();
}
}, cancellationToken);
}
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
public async IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var data in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
yield return data;
}
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
outputChannel.Writer.TryComplete();
}
disposed = true;
}
}
}

View File

@ -0,0 +1,154 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Dapr.Client.Crypto;
/// <summary>
/// Provides the implementation to encrypt a stream of plaintext data with the Dapr runtime.
/// </summary>
internal sealed class EncryptionStreamProcessor : IDisposable
{
private bool disposed;
private readonly Channel<ReadOnlyMemory<byte>> outputChannel = Channel.CreateUnbounded<ReadOnlyMemory<byte>>();
/// <summary>
/// Surfaces any exceptions encountered while asynchronously processing the inbound and outbound streams.
/// </summary>
internal event EventHandler<Exception>? OnException;
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to encrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="options">The encryption options.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
public async Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> call,
Autogenerated.EncryptRequestOptions options,
int streamingBlockSizeInBytes,
CancellationToken cancellationToken)
{
//Read from the input stream and write to the gRPC call
_ = Task.Run(async () =>
{
try
{
await using var bufferedStream = new BufferedStream(inputStream, streamingBlockSizeInBytes);
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead = await bufferedStream.ReadAsync(buffer, cancellationToken)) > 0)
{
var request = new Autogenerated.EncryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
};
//Only include the options in the first message
if (sequenceNumber == 0)
{
request.Options = options;
}
await call.RequestStream.WriteAsync(request, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Expected cancellation exception
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
await call.RequestStream.CompleteAsync();
}
}, cancellationToken);
//Start reading from the gRPC call and writing to the output channel
_ = Task.Run(async () =>
{
try
{
await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
{
await outputChannel.Writer.WriteAsync(response.Payload.Data.Memory, cancellationToken);
}
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
outputChannel.Writer.Complete();
}
}, cancellationToken);
}
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
public async IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var data in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
yield return data;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
outputChannel.Writer.TryComplete();
}
disposed = true;
}
}
}

View File

@ -1105,7 +1105,7 @@ namespace Dapr.Client
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);
/// <summary>
@ -1144,7 +1144,7 @@ namespace Dapr.Client
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, DecryptionOptions options, CancellationToken cancellationToken = default);
/// <summary>
@ -1157,7 +1157,7 @@ namespace Dapr.Client
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, CancellationToken cancellationToken = default);
#endregion

View File

@ -11,10 +11,10 @@
// limitations under the License.
// ------------------------------------------------------------------------
using Dapr.Common.Extensions;
namespace Dapr.Client;
using Crypto;
using System;
using System.Buffers;
using System.Collections.Generic;
@ -23,7 +23,6 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -1665,42 +1664,38 @@ internal class DaprClientGrpc : DaprClient
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
"As of Dapr v1.17, this method will be removed and should be used via the Dapr.Cryptography package on NuGet")]
public override async Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default)
{
if (MemoryMarshal.TryGetArray(plaintextBytes, out var plaintextSegment) && plaintextSegment.Array != null)
using var memoryStream = plaintextBytes.CreateMemoryStream(true);
var encryptionResult = EncryptAsync(vaultResourceName, memoryStream, keyName, encryptionOptions, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in encryptionResult)
{
var encryptionResult = await EncryptAsync(vaultResourceName, new MemoryStream(plaintextSegment.Array),
keyName, encryptionOptions,
cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in encryptionResult.WithCancellation(cancellationToken))
{
bufferedResult.Write(item.Span);
}
return bufferedResult.WrittenMemory;
bufferedResult.Write(item.Span);
}
throw new ArgumentException("The input instance doesn't have a valid underlying data store.",
nameof(plaintextBytes));
return bufferedResult.WrittenMemory;
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> EncryptAsync(string vaultResourceName,
"As of Dapr v1.17, this method will be removed and should be used via the Dapr.Cryptography package on NuGet")]
public override async IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
Stream plaintextStream,
string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default)
string keyName, EncryptionOptions encryptionOptions,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
ArgumentVerifier.ThrowIfNull(plaintextStream, nameof(plaintextStream));
ArgumentVerifier.ThrowIfNull(encryptionOptions, nameof(encryptionOptions));
EventHandler<Exception> exceptionHandler = (_, ex) => throw ex;
var shouldOmitDecryptionKeyName =
string.IsNullOrWhiteSpace(encryptionOptions
@ -1723,199 +1718,101 @@ internal class DaprClientGrpc : DaprClient
}
var options = CreateCallOptions(headers: null, cancellationToken);
var duplexStream = client.EncryptAlpha1(options);
var duplexStream = Client.EncryptAlpha1(options);
//Run both operations at the same time, but return the output of the streaming values coming from the operation
var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken));
return await Task.WhenAll(
//Stream the plaintext data to the sidecar in chunks
SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes,
duplexStream, encryptRequestOptions, cancellationToken),
//At the same time, retrieve the encrypted response from the sidecar
receiveResult).ContinueWith(_ => receiveResult.Result, cancellationToken);
}
/// <summary>
/// Sends the plaintext bytes in chunks to the sidecar to be encrypted.
/// </summary>
private async Task SendPlaintextStreamAsync(Stream plaintextStream,
int streamingBlockSizeInBytes,
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> duplexStream,
Autogenerated.EncryptRequestOptions encryptRequestOptions,
CancellationToken cancellationToken)
{
//Start with passing the metadata about the encryption request itself in the first message
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken);
//Send the plaintext bytes in blocks in subsequent messages
await using (var bufferedStream = new BufferedStream(plaintextStream, streamingBlockSizeInBytes))
using var streamProcessor = new EncryptionStreamProcessor();
try
{
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
streamProcessor.OnException += exceptionHandler;
await streamProcessor.ProcessStreamAsync(plaintextStream, duplexStream, encryptRequestOptions,
encryptionOptions.StreamingBlockSizeInBytes,
cancellationToken);
while ((bytesRead =
await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
cancellationToken)) !=
0)
await foreach (var value in streamProcessor.GetProcessedDataAsync(cancellationToken))
{
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.EncryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
}, cancellationToken);
//Increment the sequence number
sequenceNumber++;
yield return value;
}
}
//Send the completion message
await duplexStream.RequestStream.CompleteAsync();
}
/// <summary>
/// Retrieves the encrypted bytes from the encryption operation on the sidecar and returns as an enumerable stream.
/// </summary>
private async IAsyncEnumerable<ReadOnlyMemory<byte>> RetrieveEncryptedStreamAsync(
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> duplexStream,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken)
.ConfigureAwait(false))
finally
{
yield return encryptResponse.Payload.Data.Memory;
streamProcessor.OnException -= exceptionHandler;
}
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName,
"As of Dapr v1.17, this method will be removed and should be used via the Dapr.Cryptography package on NuGet")]
public override async IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
Stream ciphertextStream, string keyName,
DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default)
DecryptionOptions decryptionOptions,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream));
ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions));
decryptionOptions ??= new DecryptionOptions();
EventHandler<Exception> exceptionHandler = (_, ex) => throw ex;
var decryptRequestOptions = new Autogenerated.DecryptRequestOptions
{
ComponentName = vaultResourceName, KeyName = keyName
ComponentName = vaultResourceName,
KeyName = keyName
};
var options = CreateCallOptions(headers: null, cancellationToken);
var duplexStream = client.DecryptAlpha1(options);
//Run both operations at the same time, but return the output of the streaming values coming from the operation
var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken));
return await Task.WhenAll(
//Stream the ciphertext data to the sidecar in chunks
SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes,
duplexStream, decryptRequestOptions, cancellationToken),
//At the same time, retrieve the decrypted response from the sidecar
receiveResult)
//Return only the result of the `RetrieveEncryptedStreamAsync` method
.ContinueWith(t => receiveResult.Result, cancellationToken);
using var streamProcessor = new DecryptionStreamProcessor();
try
{
streamProcessor.OnException += exceptionHandler;
await streamProcessor.ProcessStreamAsync(ciphertextStream, duplexStream, decryptionOptions.StreamingBlockSizeInBytes,
decryptRequestOptions,
cancellationToken);
await foreach (var value in streamProcessor.GetProcessedDataAsync(cancellationToken))
{
yield return value;
}
}
finally
{
streamProcessor.OnException -= exceptionHandler;
}
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName,
"As of Dapr v1.17, this method will be removed and should be used via the Dapr.Cryptography package on NuGet")]
public override IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) =>
DecryptAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(),
cancellationToken);
/// <summary>
/// Sends the ciphertext bytes in chunks to the sidecar to be decrypted.
/// </summary>
private async Task SendCiphertextStreamAsync(Stream ciphertextStream,
int streamingBlockSizeInBytes,
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> duplexStream,
Autogenerated.DecryptRequestOptions decryptRequestOptions,
CancellationToken cancellationToken)
{
//Start with passing the metadata about the decryption request itself in the first message
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.DecryptRequest { Options = decryptRequestOptions }, cancellationToken);
//Send the ciphertext bytes in blocks in subsequent messages
await using (var bufferedStream = new BufferedStream(ciphertextStream, streamingBlockSizeInBytes))
{
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead =
await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
cancellationToken)) != 0)
{
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.DecryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
}, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
//Send the completion message
await duplexStream.RequestStream.CompleteAsync();
}
/// <summary>
/// Retrieves the decrypted bytes from the decryption operation on the sidecar and returns as an enumerable stream.
/// </summary>
private async IAsyncEnumerable<ReadOnlyMemory<byte>> RetrieveDecryptedStreamAsync(
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> duplexStream,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var decryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken)
.ConfigureAwait(false))
{
yield return decryptResponse.Payload.Data.Memory;
}
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
"As of Dapr v1.17, this method will be removed and should be used via the Dapr.Cryptography package on NuGet")]
public override async Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions decryptionOptions,
CancellationToken cancellationToken = default)
{
if (MemoryMarshal.TryGetArray(ciphertextBytes, out var ciphertextSegment) && ciphertextSegment.Array != null)
using var memoryStream = ciphertextBytes.CreateMemoryStream(true);
var decryptionResult = DecryptAsync(vaultResourceName, memoryStream, keyName, decryptionOptions, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in decryptionResult)
{
var decryptionResult = await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextSegment.Array),
keyName, decryptionOptions, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in decryptionResult.WithCancellation(cancellationToken))
{
bufferedResult.Write(item.Span);
}
return bufferedResult.WrittenMemory;
bufferedResult.Write(item.Span);
}
throw new ArgumentException("The input instance doesn't have a valid underlying data store",
nameof(ciphertextBytes));
return bufferedResult.WrittenMemory;
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
"As of Dapr v1.17, this method will be removed and should be used via the Dapr.Cryptography package on NuGet")]
public override async Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes, string keyName, CancellationToken cancellationToken = default) =>
await DecryptAsync(vaultResourceName, ciphertextBytes, keyName,

View File

@ -0,0 +1,36 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Dapr.Client;
internal static class ReadOnlyMemoryExtensions
{
public static MemoryStream CreateMemoryStream(this ReadOnlyMemory<byte> memory, bool isReadOnly)
{
if (memory.IsEmpty)
{
return new MemoryStream(Array.Empty<byte>(), !isReadOnly);
}
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
{
return new MemoryStream(segment.Array!, segment.Offset, segment.Count, !isReadOnly);
}
throw new ArgumentException(nameof(memory), "Unable to create MemoryStream from provided memory value");
}
}

View File

@ -77,13 +77,23 @@ internal sealed class DaprJobsGrpcClient : DaprJobsClient
ArgumentNullException.ThrowIfNull(jobName, nameof(jobName));
ArgumentNullException.ThrowIfNull(schedule, nameof(schedule));
var job = new Autogenerated.Job { Name = jobName, Schedule = schedule.ExpressionValue };
var job = new Autogenerated.Job { Name = jobName };
//Set up the schedule (recurring or point in time)
if (schedule.IsPointInTimeExpression)
{
job.DueTime = schedule.ExpressionValue;
}
else if (schedule.IsCronExpression || schedule.IsPrefixedPeriodExpression || schedule.IsDurationExpression)
{
job.Schedule = schedule.ExpressionValue;
}
if (startingFrom is not null)
{
job.DueTime = ((DateTimeOffset)startingFrom).ToString("O");
}
if (repeats is not null)
{
if (repeats < 0)
@ -153,13 +163,7 @@ internal sealed class DaprJobsGrpcClient : DaprJobsClient
var envelope = new Autogenerated.GetJobRequest { Name = jobName };
var grpcCallOptions = DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprJobsClient).Assembly, this.DaprApiToken, cancellationToken);
var response = await Client.GetJobAlpha1Async(envelope, grpcCallOptions);
return new DaprJobDetails(new DaprJobSchedule(response.Job.Schedule))
{
DueTime = response.Job.DueTime is not null ? DateTime.Parse(response.Job.DueTime) : null,
Ttl = response.Job.Ttl is not null ? DateTime.Parse(response.Job.Ttl) : null,
RepeatCount = response.Job.Repeats == default ? null : (int?)response.Job.Repeats,
Payload = response.Job.Data.ToByteArray()
};
return DeserializeJobResponse(response);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
@ -178,6 +182,29 @@ internal sealed class DaprJobsGrpcClient : DaprJobsClient
throw new DaprException("Get job operation failed: the Dapr endpoint did not return the expected value.");
}
/// <summary>
/// Testable method for performing job response deserialization.
/// </summary>
/// <remarks>
/// This is exposed strictly for testing purposes.
/// </remarks>
/// <param name="response">The job response to deserialize.</param>
/// <returns>The deserialized job response.</returns>
internal static DaprJobDetails DeserializeJobResponse(Autogenerated.GetJobResponse response)
{
var schedule = DateTime.TryParse(response.Job.DueTime, out var dueTime)
? DaprJobSchedule.FromDateTime(dueTime)
: new DaprJobSchedule(response.Job.Schedule);
return new DaprJobDetails(schedule)
{
DueTime = !string.IsNullOrWhiteSpace(response.Job.DueTime) ? DateTime.Parse(response.Job.DueTime) : null,
Ttl = !string.IsNullOrWhiteSpace(response.Job.Ttl) ? DateTime.Parse(response.Job.Ttl) : null,
RepeatCount = (int?)response.Job.Repeats ?? 0,
Payload = response.Job.Data?.ToByteArray() ?? null
};
}
/// <summary>
/// Deletes the specified job.
/// </summary>

View File

@ -11,8 +11,6 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Text.Json;
using Dapr.Jobs.Models.Responses;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
@ -29,41 +27,41 @@ public static class EndpointRouteBuilderExtensions
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="action">The asynchronous action provided by the developer that handles any inbound requests. The first two
/// parameters must be a nullable <see cref="string"/> for the jobName and a nullable <see cref="DaprJobDetails"/> with the
/// payload details, but otherwise can be populated with additional services to be injected into the delegate.</param>
/// <param name="cancellationToken">Cancellation token that will be passed in as the last parameter to the delegate action.</param>
/// parameters must be a <see cref="string"/> for the jobName and the originally registered ReadOnlyMemory&lt;byte&gt; with the
/// payload value, but otherwise can be populated with additional services to be injected into the delegate.</param>
/// <param name="timeout">Optional timeout to apply to a per-request cancellation token.</param>
public static IEndpointRouteBuilder MapDaprScheduledJobHandler(this IEndpointRouteBuilder endpoints,
Delegate action, CancellationToken cancellationToken = default)
Delegate action, TimeSpan? timeout = null)
{
ArgumentNullException.ThrowIfNull(endpoints, nameof(endpoints));
ArgumentNullException.ThrowIfNull(action, nameof(action));
endpoints.MapPost("/job/{jobName}", async context =>
{
var jobName = (string?)context.Request.RouteValues["jobName"];
DaprJobDetails? jobPayload = null;
if (context.Request.ContentLength is > 0)
//Retrieve the name of the job from the request path
var jobName = string.Empty;
if (context.Request.RouteValues.TryGetValue("jobName", out var capturedJobName))
{
using var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
try
{
var deserializedJobPayload = JsonSerializer.Deserialize<DeserializableDaprJobDetails>(body);
jobPayload = deserializedJobPayload?.ToType() ?? null;
}
catch (JsonException)
{
jobPayload = null;
}
jobName = (string)capturedJobName!;
}
var parameters = new Dictionary<Type, object?>
//Retrieve the job payload from the request body
ReadOnlyMemory<byte> payload = new();
if (context.Request.ContentLength is > 0)
{
using var streamContent = new StreamContent(context.Request.Body);
payload = await streamContent.ReadAsByteArrayAsync(CancellationToken.None);
}
using var cts = timeout.HasValue
? new CancellationTokenSource(timeout.Value)
: new CancellationTokenSource();
var parameters = new Dictionary<Type, object>
{
{ typeof(string), jobName },
{ typeof(DaprJobDetails), jobPayload },
{ typeof(CancellationToken), CancellationToken.None }
{ typeof(ReadOnlyMemory<byte>), payload },
{ typeof(CancellationToken), cts.Token }
};
var actionParameters = action.Method.GetParameters();

View File

@ -67,7 +67,7 @@ internal static class TimeSpanExtensions
/// <returns>True if the string represents a parseable interval duration; false if not.</returns>
public static bool IsDurationString(this string interval)
{
interval = interval.Replace("ms", "q");
interval = interval.Replace("ms", "q").Replace("@every ", string.Empty);
return hourRegex.Match(interval).Success ||
minuteRegex.Match(interval).Success ||
secondRegex.Match(interval).Success ||

View File

@ -65,11 +65,8 @@ public sealed class DaprJobSchedule
/// </summary>
/// <param name="scheduledTime">The date and time when the job should be triggered.</param>
/// <returns></returns>
public static DaprJobSchedule FromDateTime(DateTimeOffset scheduledTime)
{
return new DaprJobSchedule(scheduledTime.ToString("O"));
}
public static DaprJobSchedule FromDateTime(DateTimeOffset scheduledTime) => new(scheduledTime.ToString("O"));
/// <summary>
/// Specifies a schedule using a Cron-like expression or '@' prefixed period strings.
/// </summary>
@ -86,10 +83,7 @@ public sealed class DaprJobSchedule
/// Specifies a schedule using a duration interval articulated via a <see cref="TimeSpan"/>.
/// </summary>
/// <param name="duration">The duration interval.</param>
public static DaprJobSchedule FromDuration(TimeSpan duration)
{
return new DaprJobSchedule(duration.ToDurationString());
}
public static DaprJobSchedule FromDuration(TimeSpan duration) => new($"@every {duration.ToDurationString()}");
/// <summary>
/// Specifies a schedule in which the job is triggered to run once a year.
@ -104,7 +98,7 @@ public sealed class DaprJobSchedule
/// <summary>
/// Specifies a schedule in which the job is triggered weekly.
/// </summary>
public static DaprJobSchedule Weekly { get; } =new DaprJobSchedule("@weekly");
public static DaprJobSchedule Weekly { get; } = new DaprJobSchedule("@weekly");
/// <summary>
/// Specifies a schedule in which the job is triggered daily.

View File

@ -11,9 +11,6 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Text.Json.Serialization;
using Dapr.Jobs.JsonConverters;
namespace Dapr.Jobs.Models.Responses;
/// <summary>
@ -46,52 +43,3 @@ public sealed record DaprJobDetails(DaprJobSchedule Schedule)
/// </summary>
public byte[]? Payload { get; init; } = null;
}
/// <summary>
/// A deserializable version of the <see cref="DaprJobDetails"/>.
/// </summary>
internal sealed record DeserializableDaprJobDetails
{
/// <summary>
/// Represents the schedule that triggers the job.
/// </summary>
public string? Schedule { get; init; }
/// <summary>
/// Allows for jobs with fixed repeat counts.
/// </summary>
public int? RepeatCount { get; init; } = null;
/// <summary>
/// Identifies a point-in-time representing when the job schedule should start from,
/// or as a "one-shot" time if other scheduling fields are not provided.
/// </summary>
[JsonConverter(typeof(Iso8601DateTimeJsonConverter))]
public DateTimeOffset? DueTime { get; init; } = null;
/// <summary>
/// A point-in-time value representing with the job should expire.
/// </summary>
/// <remarks>
/// This must be greater than <see cref="DueTime"/> if both are set.
/// </remarks>
[JsonConverter(typeof(Iso8601DateTimeJsonConverter))]
public DateTimeOffset? Ttl { get; init; } = null;
/// <summary>
/// Stores the main payload of the job which is passed to the trigger function.
/// </summary>
public byte[]? Payload { get; init; } = null;
public DaprJobDetails ToType()
{
var schedule = DaprJobSchedule.FromExpression(Schedule ?? string.Empty);
return new DaprJobDetails(schedule)
{
DueTime = DueTime,
Payload = Payload,
RepeatCount = RepeatCount,
Ttl = Ttl
};
}
}

View File

@ -1225,7 +1225,6 @@ message Job {
// Systemd timer style cron accepts 6 fields:
// seconds | minutes | hours | day of month | month | day of week
// 0-59 | 0-59 | 0-23 | 1-31 | 1-12/jan-dec | 0-6/sun-sat
//
// "0 30 * * * *" - every hour on the half hour
// "0 15 3 * * *" - every day at 03:15
@ -1320,8 +1319,8 @@ message ConversationRequest {
}
message ConversationInput {
// The message to send to the llm
string message = 1;
// The content to send to the llm
string content = 1;
// The role to set for the message
optional string role = 2;
@ -1345,4 +1344,4 @@ message ConversationResponse {
// An array of results.
repeated ConversationResult outputs = 2;
}
}

View File

@ -7,7 +7,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions"/>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />

View File

@ -4,7 +4,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="xunit" />

View File

@ -9,7 +9,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />

View File

@ -11,44 +11,43 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Test
namespace Dapr.Actors.Test;
using System.Threading.Tasks;
using Dapr.Actors.Builder;
using Dapr.Actors.Communication;
using Dapr.Actors.Description;
using Dapr.Actors.Runtime;
using Xunit;
/// <summary>
/// Test class for Actor Code builder.
/// </summary>
public class ActorCodeBuilderTests
{
using System.Threading.Tasks;
using Dapr.Actors.Builder;
using Dapr.Actors.Communication;
using Dapr.Actors.Description;
using Dapr.Actors.Runtime;
using Xunit;
/// <summary>
/// Test class for Actor Code builder.
/// Tests Proxy Generation.
/// </summary>
public class ActorCodeBuilderTests
[Fact]
public void TestBuildActorProxyGenerator()
{
/// <summary>
/// Tests Proxy Generation.
/// </summary>
[Fact]
public void TestBuildActorProxyGenerator()
{
ActorCodeBuilder.GetOrCreateProxyGenerator(typeof(ITestActor));
}
ActorCodeBuilder.GetOrCreateProxyGenerator(typeof(ITestActor));
}
[Fact]
public async Task ActorCodeBuilder_BuildDispatcher()
{
var host = ActorHost.CreateForTest<TestActor>();
[Fact]
public async Task ActorCodeBuilder_BuildDispatcher()
{
var host = ActorHost.CreateForTest<TestActor>();
var dispatcher = ActorCodeBuilder.GetOrCreateMethodDispatcher(typeof(ITestActor));
var methodId = MethodDescription.Create("test", typeof(ITestActor).GetMethod("GetCountAsync"), true).Id;
var dispatcher = ActorCodeBuilder.GetOrCreateMethodDispatcher(typeof(ITestActor));
var methodId = MethodDescription.Create("test", typeof(ITestActor).GetMethod("GetCountAsync"), true).Id;
var impl = new TestActor(host);
var request = new ActorRequestMessageBody(0);
var response = new WrappedRequestMessageFactory();
var impl = new TestActor(host);
var request = new ActorRequestMessageBody(0);
var response = new WrappedRequestMessageFactory();
var body = (WrappedMessage)await dispatcher.DispatchAsync(impl, methodId, request, response, default);
dynamic bodyValue = body.Value;
Assert.Equal(5, (int)bodyValue.retVal);
}
var body = (WrappedMessage)await dispatcher.DispatchAsync(impl, methodId, request, response, default);
dynamic bodyValue = body.Value;
Assert.Equal(5, (int)bodyValue.retVal);
}
}

View File

@ -11,194 +11,193 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Test
namespace Dapr.Actors.Test;
using System;
using System.Collections.Generic;
using Xunit;
/// <summary>
/// Contains tests for Actor ID.
/// </summary>
public class ActorIdTests
{
using System;
using System.Collections.Generic;
using Xunit;
public static readonly IEnumerable<object[]> CompareToValues = new List<object[]>
{
new object[]
{
new ActorId("1"),
null,
1,
},
new object[]
{
new ActorId("1"),
new ActorId("1"),
0,
},
new object[]
{
new ActorId("1"),
new ActorId("2"),
-1,
},
new object[]
{
new ActorId("2"),
new ActorId("1"),
1,
},
};
public static readonly IEnumerable<object[]> EqualsValues = new List<object[]>
{
new object[]
{
new ActorId("1"),
null,
false,
},
new object[]
{
new ActorId("1"),
new ActorId("1"),
true,
},
new object[]
{
new ActorId("1"),
new ActorId("2"),
false,
},
};
public static readonly IEnumerable<object[]> EqualsOperatorValues = new List<object[]>
{
new object[]
{
null,
null,
true,
},
new object[]
{
new ActorId("1"),
null,
false,
},
new object[]
{
null,
new ActorId("1"),
false,
},
new object[]
{
new ActorId("1"),
new ActorId("1"),
true,
},
new object[]
{
new ActorId("1"),
new ActorId("2"),
false,
},
};
/// <summary>
/// Contains tests for Actor ID.
/// Throw exception if id is null.
/// </summary>
public class ActorIdTests
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Initialize_New_ActorId_Object_With_Null_Or_Whitespace_Id(string id)
{
public static readonly IEnumerable<object[]> CompareToValues = new List<object[]>
{
new object[]
{
new ActorId("1"),
null,
1,
},
new object[]
{
new ActorId("1"),
new ActorId("1"),
0,
},
new object[]
{
new ActorId("1"),
new ActorId("2"),
-1,
},
new object[]
{
new ActorId("2"),
new ActorId("1"),
1,
},
};
Assert.Throws<ArgumentException>(() => new ActorId(id));
}
public static readonly IEnumerable<object[]> EqualsValues = new List<object[]>
{
new object[]
{
new ActorId("1"),
null,
false,
},
new object[]
{
new ActorId("1"),
new ActorId("1"),
true,
},
new object[]
{
new ActorId("1"),
new ActorId("2"),
false,
},
};
[Theory]
[InlineData("one")]
[InlineData("123")]
public void Get_Id(string id)
{
ActorId actorId = new ActorId(id);
Assert.Equal(id, actorId.GetId());
}
public static readonly IEnumerable<object[]> EqualsOperatorValues = new List<object[]>
{
new object[]
{
null,
null,
true,
},
new object[]
{
new ActorId("1"),
null,
false,
},
new object[]
{
null,
new ActorId("1"),
false,
},
new object[]
{
new ActorId("1"),
new ActorId("1"),
true,
},
new object[]
{
new ActorId("1"),
new ActorId("2"),
false,
},
};
[Theory]
[InlineData("one")]
[InlineData("123")]
public void Verify_ToString(string id)
{
ActorId actorId = new ActorId(id);
Assert.Equal(id, actorId.ToString());
}
/// <summary>
/// Throw exception if id is null.
/// </summary>
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Initialize_New_ActorId_Object_With_Null_Or_Whitespace_Id(string id)
{
Assert.Throws<ArgumentException>(() => new ActorId(id));
}
/// <summary>
/// Verify Equals method by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsValues))]
public void Verify_Equals_By_Object(object id1, object id2, bool expectedValue)
{
Assert.Equal(expectedValue, id1.Equals(id2));
}
[Theory]
[InlineData("one")]
[InlineData("123")]
public void Get_Id(string id)
{
ActorId actorId = new ActorId(id);
Assert.Equal(id, actorId.GetId());
}
/// <summary>
/// Verify Equals method by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsValues))]
public void Verify_Equals_By_ActorId(ActorId id1, ActorId id2, bool expectedValue)
{
Assert.Equal(expectedValue, id1.Equals(id2));
}
[Theory]
[InlineData("one")]
[InlineData("123")]
public void Verify_ToString(string id)
{
ActorId actorId = new ActorId(id);
Assert.Equal(id, actorId.ToString());
}
/// <summary>
/// Verify equals operator by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare, or null.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsOperatorValues))]
public void Verify_Equals_Operator(ActorId id1, ActorId id2, bool expectedValue)
{
Assert.Equal(expectedValue, id1 == id2);
}
/// <summary>
/// Verify Equals method by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsValues))]
public void Verify_Equals_By_Object(object id1, object id2, bool expectedValue)
{
Assert.Equal(expectedValue, id1.Equals(id2));
}
/// <summary>
/// Verify not equals operator by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare, or null.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsOperatorValues))]
public void Verify_Not_Equals_Operator(ActorId id1, ActorId id2, bool expectedValue)
{
Assert.Equal(!expectedValue, id1 != id2);
}
/// <summary>
/// Verify Equals method by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsValues))]
public void Verify_Equals_By_ActorId(ActorId id1, ActorId id2, bool expectedValue)
{
Assert.Equal(expectedValue, id1.Equals(id2));
}
/// <summary>
/// Verify equals operator by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare, or null.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsOperatorValues))]
public void Verify_Equals_Operator(ActorId id1, ActorId id2, bool expectedValue)
{
Assert.Equal(expectedValue, id1 == id2);
}
/// <summary>
/// Verify not equals operator by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare, or null.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(EqualsOperatorValues))]
public void Verify_Not_Equals_Operator(ActorId id1, ActorId id2, bool expectedValue)
{
Assert.Equal(!expectedValue, id1 != id2);
}
/// <summary>
/// Verify CompareTo method by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(CompareToValues))]
public void Verify_CompareTo(ActorId id1, ActorId id2, int expectedValue)
{
Assert.Equal(expectedValue, id1.CompareTo(id2));
}
/// <summary>
/// Verify CompareTo method by comparing two actorIds.
/// </summary>
/// <param name="id1">The first actorId to compare.</param>
/// <param name="id2">The second actorId to compare, or null.</param>
/// <param name="expectedValue">Expected value from comparison.</param>
[Theory]
[MemberData(nameof(CompareToValues))]
public void Verify_CompareTo(ActorId id1, ActorId id2, int expectedValue)
{
Assert.Equal(expectedValue, id1.CompareTo(id2));
}
}

View File

@ -11,51 +11,50 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Test
namespace Dapr.Actors.Test;
using System;
using System.IO;
using Shouldly;
using Xunit;
/// <summary>
/// Contains tests for Actor method invocation exceptions.
/// </summary>
public class ActorMethodInvocationExceptionTests
{
using System;
using System.IO;
using FluentAssertions;
using Xunit;
/// <summary>
/// Contains tests for Actor method invocation exceptions.
/// This test will verify:
/// 1) the path for serialization and deserialization of the remote exception
/// 2) and validating the inner exception.
/// </summary>
public class ActorMethodInvocationExceptionTests
[Fact]
public void TestThrowActorMethodInvocationException()
{
/// <summary>
/// This test will verify:
/// 1) the path for serialization and deserialization of the remote exception
/// 2) and validating the inner exception.
/// </summary>
[Fact]
public void TestThrowActorMethodInvocationException()
{
// Create Remote Actor Method test Exception
var message = "Remote Actor encountered an exception";
var innerMessage = "Bad time zone";
var exception = new InvalidOperationException(message, new InvalidTimeZoneException(innerMessage));
// Create Remote Actor Method test Exception
var message = "Remote Actor encountered an exception";
var innerMessage = "Bad time zone";
var exception = new InvalidOperationException(message, new InvalidTimeZoneException(innerMessage));
// Create Serialized Exception
var serializedException = ActorInvokeException.FromException(exception);
// Create Serialized Exception
var serializedException = ActorInvokeException.FromException(exception);
// De Serialize Exception
var isDeserialzied = ActorInvokeException.ToException(
new MemoryStream(serializedException),
out var remoteMethodException);
isDeserialzied.Should().BeTrue();
var ex = this.ThrowRemoteException(message, remoteMethodException);
ex.Should().BeOfType<ActorMethodInvocationException>();
ex.InnerException.Should().BeOfType<ActorInvokeException>();
((ActorInvokeException)ex.InnerException).ActualExceptionType.Should().Be("System.InvalidOperationException");
ex.InnerException.InnerException.Should().BeNull();
ex.Message.Should().Be(message);
ex.InnerException.Message.Should().Be(message);
}
// De Serialize Exception
var isDeserialzied = ActorInvokeException.ToException(
new MemoryStream(serializedException),
out var remoteMethodException);
isDeserialzied.ShouldBeTrue();
var ex = this.ThrowRemoteException(message, remoteMethodException);
ex.ShouldBeOfType<ActorMethodInvocationException>();
ex.InnerException.ShouldBeOfType<ActorInvokeException>();
((ActorInvokeException)ex.InnerException).ActualExceptionType.ShouldBe("System.InvalidOperationException");
ex.InnerException.InnerException.ShouldBeNull();
ex.Message.ShouldBe(message);
ex.InnerException.Message.ShouldBe(message);
}
private Exception ThrowRemoteException(string message, Exception exception)
{
return new ActorMethodInvocationException(message, exception, false);
}
private Exception ThrowRemoteException(string message, Exception exception)
{
return new ActorMethodInvocationException(message, exception, false);
}
}

View File

@ -11,31 +11,30 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Client
namespace Dapr.Actors.Client;
using System;
using Shouldly;
using Xunit;
/// <summary>
/// Test class for Actor Code builder.
/// </summary>
public class ActorProxyOptionsTests
{
using System;
using FluentAssertions;
using Xunit;
/// <summary>
/// Test class for Actor Code builder.
/// </summary>
public class ActorProxyOptionsTests
[Fact]
public void DefaultConstructor_Succeeds()
{
[Fact]
public void DefaultConstructor_Succeeds()
{
var options = new ActorProxyOptions();
Assert.NotNull(options);
}
var options = new ActorProxyOptions();
Assert.NotNull(options);
}
[Fact]
public void SerializerOptionsCantBeNull_Fails()
{
var options = new ActorProxyOptions();
Action action = () => options.JsonSerializerOptions = null;
[Fact]
public void SerializerOptionsCantBeNull_Fails()
{
var options = new ActorProxyOptions();
Action action = () => options.JsonSerializerOptions = null;
action.Should().Throw<ArgumentNullException>();
}
action.ShouldThrow<ArgumentNullException>();
}
}

View File

@ -11,113 +11,112 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Client
namespace Dapr.Actors.Client;
using System;
using System.Text.Json;
using Dapr.Actors.Test;
using Shouldly;
using Xunit;
/// <summary>
/// Test class for Actor Code builder.
/// </summary>
public class ActorProxyTests
{
using System;
using System.Text.Json;
using Dapr.Actors.Test;
using FluentAssertions;
using Xunit;
/// <summary>
/// Test class for Actor Code builder.
/// Tests Proxy Creation.
/// </summary>
public class ActorProxyTests
[Fact]
public void Create_WithIdAndActorTypeString_Succeeds()
{
/// <summary>
/// Tests Proxy Creation.
/// </summary>
[Fact]
public void Create_WithIdAndActorTypeString_Succeeds()
var actorId = new ActorId("abc");
var proxy = ActorProxy.Create(actorId, "TestActor");
Assert.NotNull(proxy);
}
[Fact]
public void Create_WithValidActorInterface_Succeeds()
{
var actorId = new ActorId("abc");
var proxy = ActorProxy.Create(actorId, typeof(ITestActor), "TestActor");
Assert.NotNull(proxy);
}
[Fact]
public void Create_WithInvalidType_ThrowsArgumentException()
{
var actorId = new ActorId("abc");
Action action = () => ActorProxy.Create(actorId, typeof(ActorId), "TestActor");
action.ShouldThrow<ArgumentException>();
}
[Fact]
public void Create_WithCustomSerializerOnDefaultActorProxyFactory_Succeeds()
{
var factory = new ActorProxyFactory();
factory.DefaultOptions.JsonSerializerOptions = new JsonSerializerOptions();
var actorId = new ActorId("abc");
var proxy = (ActorProxy)factory.Create(actorId, "TestActor");
Assert.Same(factory.DefaultOptions.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void Create_WithCustomSerializerArgument_Succeeds()
{
var options = new ActorProxyOptions()
{
var actorId = new ActorId("abc");
var proxy = ActorProxy.Create(actorId, "TestActor");
Assert.NotNull(proxy);
}
JsonSerializerOptions = new JsonSerializerOptions()
};
[Fact]
public void Create_WithValidActorInterface_Succeeds()
var actorId = new ActorId("abc");
var proxy = (ActorProxy)ActorProxy.Create(actorId, typeof(ITestActor), "TestActor", options);
Assert.Same(options.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void CreateGeneric_WithValidActorInterface_Succeeds()
{
var actorId = new ActorId("abc");
var proxy = ActorProxy.Create<ITestActor>(actorId, "TestActor");
Assert.NotNull(proxy);
}
[Fact]
public void CreateGeneric_WithCustomSerializerOnDefaultActorProxyFactory_Succeeds()
{
var factory = new ActorProxyFactory();
factory.DefaultOptions.JsonSerializerOptions = new JsonSerializerOptions();
var actorId = new ActorId("abc");
var proxy = (ActorProxy)factory.CreateActorProxy<ITestActor>(actorId, "TestActor");
Assert.Same(factory.DefaultOptions.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void CreateGeneric_WithCustomSerializerArgument_Succeeds()
{
var options = new ActorProxyOptions()
{
var actorId = new ActorId("abc");
var proxy = ActorProxy.Create(actorId, typeof(ITestActor), "TestActor");
Assert.NotNull(proxy);
}
JsonSerializerOptions = new JsonSerializerOptions()
};
[Fact]
public void Create_WithInvalidType_ThrowsArgumentException()
{
var actorId = new ActorId("abc");
Action action = () => ActorProxy.Create(actorId, typeof(ActorId), "TestActor");
action.Should().Throw<ArgumentException>();
}
var actorId = new ActorId("abc");
var proxy = (ActorProxy)ActorProxy.Create<ITestActor>(actorId, "TestActor", options);
[Fact]
public void Create_WithCustomSerializerOnDefaultActorProxyFactory_Succeeds()
{
var factory = new ActorProxyFactory();
factory.DefaultOptions.JsonSerializerOptions = new JsonSerializerOptions();
Assert.Same(options.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
var actorId = new ActorId("abc");
var proxy = (ActorProxy)factory.Create(actorId, "TestActor");
[Fact]
public void SetActorProxyFactoryDefaultOptions_ToNull_ThrowsArgumentNullException()
{
var factory = new ActorProxyFactory();
Action action = () => factory.DefaultOptions = null;
Assert.Same(factory.DefaultOptions.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void Create_WithCustomSerializerArgument_Succeeds()
{
var options = new ActorProxyOptions()
{
JsonSerializerOptions = new JsonSerializerOptions()
};
var actorId = new ActorId("abc");
var proxy = (ActorProxy)ActorProxy.Create(actorId, typeof(ITestActor), "TestActor", options);
Assert.Same(options.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void CreateGeneric_WithValidActorInterface_Succeeds()
{
var actorId = new ActorId("abc");
var proxy = ActorProxy.Create<ITestActor>(actorId, "TestActor");
Assert.NotNull(proxy);
}
[Fact]
public void CreateGeneric_WithCustomSerializerOnDefaultActorProxyFactory_Succeeds()
{
var factory = new ActorProxyFactory();
factory.DefaultOptions.JsonSerializerOptions = new JsonSerializerOptions();
var actorId = new ActorId("abc");
var proxy = (ActorProxy)factory.CreateActorProxy<ITestActor>(actorId, "TestActor");
Assert.Same(factory.DefaultOptions.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void CreateGeneric_WithCustomSerializerArgument_Succeeds()
{
var options = new ActorProxyOptions()
{
JsonSerializerOptions = new JsonSerializerOptions()
};
var actorId = new ActorId("abc");
var proxy = (ActorProxy)ActorProxy.Create<ITestActor>(actorId, "TestActor", options);
Assert.Same(options.JsonSerializerOptions, proxy.JsonSerializerOptions);
}
[Fact]
public void SetActorProxyFactoryDefaultOptions_ToNull_ThrowsArgumentNullException()
{
var factory = new ActorProxyFactory();
Action action = () => factory.DefaultOptions = null;
action.Should().Throw<ArgumentNullException>();
}
action.ShouldThrow<ArgumentNullException>();
}
}

View File

@ -1,93 +1,105 @@
using System;
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using Dapr.Actors.Client;
using Dapr.Actors.Runtime;
using Dapr.Actors.Test;
using Xunit;
namespace Dapr.Actors
namespace Dapr.Actors;
public class ActorReferenceTests
{
public class ActorReferenceTests
[Fact]
public void Get_WhenActorIsNull_ReturnsNull()
{
[Fact]
public void Get_WhenActorIsNull_ReturnsNull()
{
// Arrange
object actor = null;
// Arrange
object actor = null;
// Act
var result = ActorReference.Get(actor);
// Act
var result = ActorReference.Get(actor);
// Assert
Assert.Null(result);
}
[Fact]
public void Get_FromActorProxy_ReturnsActorReference()
{
// Arrange
var expectedActorId = new ActorId("abc");
var expectedActorType = "TestActor";
var proxy = ActorProxy.Create(expectedActorId, typeof(ITestActor), expectedActorType);
// Act
var actorReference = ActorReference.Get(proxy);
// Assert
Assert.NotNull(actorReference);
Assert.Equal(expectedActorId, actorReference.ActorId);
Assert.Equal(expectedActorType, actorReference.ActorType);
}
[Fact]
public async Task Get_FromActorImplementation_ReturnsActorReference()
{
// Arrange
var expectedActorId = new ActorId("abc");
var expectedActorType = nameof(ActorReferenceTestActor);
var host = ActorHost.CreateForTest<ActorReferenceTestActor>(new ActorTestOptions() { ActorId = expectedActorId });
var actor = new ActorReferenceTestActor(host);
// Act
var actorReference = await actor.GetActorReference();
// Assert
Assert.NotNull(actorReference);
Assert.Equal(expectedActorId, actorReference.ActorId);
Assert.Equal(expectedActorType, actorReference.ActorType);
}
[Fact]
public void Get_WithInvalidObjectType_ThrowArgumentOutOfRangeException()
{
// Arrange
var actor = new object();
// Act
var act = () => ActorReference.Get(actor);
// Assert
var exception = Assert.Throws<ArgumentOutOfRangeException>(act);
Assert.Equal("actor", exception.ParamName);
Assert.Equal("Invalid actor object type. (Parameter 'actor')", exception.Message);
}
// Assert
Assert.Null(result);
}
public interface IActorReferenceTestActor : IActor
[Fact]
public void Get_FromActorProxy_ReturnsActorReference()
{
Task<ActorReference> GetActorReference();
// Arrange
var expectedActorId = new ActorId("abc");
var expectedActorType = "TestActor";
var proxy = ActorProxy.Create(expectedActorId, typeof(ITestActor), expectedActorType);
// Act
var actorReference = ActorReference.Get(proxy);
// Assert
Assert.NotNull(actorReference);
Assert.Equal(expectedActorId, actorReference.ActorId);
Assert.Equal(expectedActorType, actorReference.ActorType);
}
public class ActorReferenceTestActor : Actor, IActorReferenceTestActor
[Fact]
public async Task Get_FromActorImplementation_ReturnsActorReference()
{
public ActorReferenceTestActor(ActorHost host)
: base(host)
{
}
// Arrange
var expectedActorId = new ActorId("abc");
var expectedActorType = nameof(ActorReferenceTestActor);
var host = ActorHost.CreateForTest<ActorReferenceTestActor>(new ActorTestOptions() { ActorId = expectedActorId });
var actor = new ActorReferenceTestActor(host);
public Task<ActorReference> GetActorReference()
{
return Task.FromResult(ActorReference.Get(this));
}
// Act
var actorReference = await actor.GetActorReference();
// Assert
Assert.NotNull(actorReference);
Assert.Equal(expectedActorId, actorReference.ActorId);
Assert.Equal(expectedActorType, actorReference.ActorType);
}
[Fact]
public void Get_WithInvalidObjectType_ThrowArgumentOutOfRangeException()
{
// Arrange
var actor = new object();
// Act
var act = () => ActorReference.Get(actor);
// Assert
var exception = Assert.Throws<ArgumentOutOfRangeException>(act);
Assert.Equal("actor", exception.ParamName);
Assert.Equal("Invalid actor object type. (Parameter 'actor')", exception.Message);
}
}
public interface IActorReferenceTestActor : IActor
{
Task<ActorReference> GetActorReference();
}
public class ActorReferenceTestActor : Actor, IActorReferenceTestActor
{
public ActorReferenceTestActor(ActorHost host)
: base(host)
{
}
public Task<ActorReference> GetActorReference()
{
return Task.FromResult(ActorReference.Get(this));
}
}

View File

@ -69,7 +69,7 @@ namespace Dapr.Actors
}
[Fact]
public async Task CanTestStartingAndStoppinReminder()
public async Task CanTestStartingAndStoppingReminder()
{
var reminders = new List<ActorReminder>();
IActorReminder getReminder = null;
@ -120,6 +120,22 @@ namespace Dapr.Actors
Assert.Empty(reminders);
}
[Fact]
public async Task ReminderReturnsNullIfNotAvailable()
{
var timerManager = new Mock<ActorTimerManager>(MockBehavior.Strict);
timerManager
.Setup(tm => tm.GetReminderAsync(It.IsAny<ActorReminderToken>()))
.Returns(() => Task.FromResult<IActorReminder>(null));
var host = ActorHost.CreateForTest<CoolTestActor>(new ActorTestOptions() { TimerManager = timerManager.Object, });
var actor = new CoolTestActor(host);
//There is no starting reminder, so this should always return null
var retrievedReminder = await actor.GetReminderAsync();
Assert.Null(retrievedReminder);
}
public interface ICoolTestActor : IActor
{
}

View File

@ -15,7 +15,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Client;
using FluentAssertions;
using Shouldly;
using Xunit;
namespace Dapr.Actors.Test
@ -43,7 +43,7 @@ namespace Dapr.Actors.Test
request.Dismiss();
var headerValues = request.Request.Headers.GetValues("dapr-api-token");
headerValues.Should().Contain("test_token");
headerValues.ShouldContain("test_token");
}
[Fact(Skip = "https://github.com/dapr/dotnet-sdk/issues/596")]
@ -89,7 +89,7 @@ namespace Dapr.Actors.Test
request.Dismiss();
var headerValues = request.Request.Headers.GetValues("dapr-api-token");
headerValues.Should().Contain("test_token");
headerValues.ShouldContain("test_token");
}
[Fact]

View File

@ -0,0 +1,35 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using Dapr.Actors.Runtime;
using Xunit;
namespace Dapr.Actors;
public class ConverterUtilsTests
{
[Fact]
public void Deserialize_Period_Duration1()
{
var result = ConverterUtils.ConvertTimeSpanValueFromISO8601Format("@every 15m");
Assert.Equal(TimeSpan.FromMinutes(15), result.Item1);
}
[Fact]
public void Deserialize_Period_Duration2()
{
var result = ConverterUtils.ConvertTimeSpanValueFromISO8601Format("@hourly");
Assert.Equal(TimeSpan.FromHours(1), result.Item1);
}
}

View File

@ -9,10 +9,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="Shouldly"/>
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>

View File

@ -21,7 +21,7 @@ namespace Dapr.Actors.Test
using System.Security.Authentication;
using System.Text.Json;
using System.Threading.Tasks;
using FluentAssertions;
using Shouldly;
using Xunit;
/// <summary>
@ -34,9 +34,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var keyName = "StateKey_Test";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string keyName = "StateKey_Test";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -48,8 +48,8 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateKeyRelativeUrlFormat, actorType, actorId, keyName);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Get);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Get);
}
[Fact]
@ -57,9 +57,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var data = "StateData";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string data = "StateData";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -71,8 +71,8 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateRelativeUrlFormat, actorType, actorId);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Put);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Put);
}
[Fact]
@ -80,10 +80,10 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var methodName = "MethodName";
var payload = "JsonData";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string methodName = "MethodName";
const string payload = "JsonData";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -95,8 +95,8 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorMethodRelativeUrlFormat, actorType, actorId, methodName);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Put);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Put);
}
[Fact]
@ -104,10 +104,10 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var reminderName = "ReminderName";
var payload = "JsonData";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string reminderName = "ReminderName";
const string payload = "JsonData";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -119,8 +119,8 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Put);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Put);
}
[Fact]
@ -128,9 +128,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var reminderName = "ReminderName";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string reminderName = "ReminderName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -142,8 +142,32 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Delete);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Delete);
}
[Fact]
public async Task GetReminder_ValidateRequest()
{
await using var client = TestClient.CreateForDaprHttpInterator();
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string reminderName = "ReminderName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
await httpInteractor.GetReminderAsync(actorType, actorId, reminderName);
});
request.Dismiss();
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat,
actorType, actorId, reminderName);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Get);
}
[Fact]
@ -151,10 +175,10 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var timerName = "TimerName";
var payload = "JsonData";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string timerName = "TimerName";
const string payload = "JsonData";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -166,8 +190,8 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Put);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Put);
}
[Fact]
@ -189,8 +213,8 @@ namespace Dapr.Actors.Test
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName);
actualPath.Should().Be(expectedPath);
request.Request.Method.Should().Be(HttpMethod.Delete);
actualPath.ShouldBe(expectedPath);
request.Request.Method.ShouldBe(HttpMethod.Delete);
}
[Fact]
@ -198,9 +222,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator(apiToken: "test_token");
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var timerName = "TimerName";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string timerName = "TimerName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -210,8 +234,8 @@ namespace Dapr.Actors.Test
request.Dismiss();
request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
headerValues.Count().Should().Be(1);
headerValues.First().Should().Be("test_token");
headerValues.Count().ShouldBe(1);
headerValues.First().ShouldBe("test_token");
}
[Fact]
@ -219,9 +243,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var timerName = "TimerName";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string timerName = "TimerName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -231,7 +255,7 @@ namespace Dapr.Actors.Test
request.Dismiss();
request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
headerValues.Should().BeNull();
headerValues.ShouldBeNull();
}
[Fact]
@ -239,9 +263,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var timerName = "TimerName";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string timerName = "TimerName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -272,9 +296,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var timerName = "TimerName";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string timerName = "TimerName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -294,9 +318,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var timerName = "TimerName";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string timerName = "TimerName";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -316,10 +340,10 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var methodName = "MethodName";
var payload = "JsonData";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string methodName = "MethodName";
const string payload = "JsonData";
ActorReentrancyContextAccessor.ReentrancyContext = "1";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
@ -337,10 +361,10 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var methodName = "MethodName";
var payload = "JsonData";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string methodName = "MethodName";
const string payload = "JsonData";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -356,9 +380,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var keyName = "StateKey_Test";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string keyName = "StateKey_Test";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{
@ -385,9 +409,9 @@ namespace Dapr.Actors.Test
{
await using var client = TestClient.CreateForDaprHttpInterator();
var actorType = "ActorType_Test";
var actorId = "ActorId_Test";
var keyName = "StateKey_Test";
const string actorType = "ActorType_Test";
const string actorId = "ActorId_Test";
const string keyName = "StateKey_Test";
var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
{

View File

@ -1,20 +1,19 @@
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Shouldly;
using Xunit;
namespace Dapr.Actors.Description
@ -31,13 +30,12 @@ namespace Dapr.Actors.Description
var description = ActorInterfaceDescription.Create(type);
// Assert
description.Should().NotBeNull();
description.ShouldNotBeNull();
using var _ = new AssertionScope();
description.InterfaceType.Should().Be(type);
description.Id.Should().NotBe(0);
description.V1Id.Should().Be(0);
description.Methods.Should().HaveCount(2);
description.InterfaceType.ShouldBe(type);
description.Id.ShouldNotBe(0);
description.V1Id.ShouldBe(0);
description.Methods.Length.ShouldBe(2);
}
[Fact]
@ -50,10 +48,9 @@ namespace Dapr.Actors.Description
Action action = () => ActorInterfaceDescription.Create(type);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The type 'System.Object' is not an Actor interface as it is not an interface.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The type 'System.Object' is not an Actor interface as it is not an interface.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -66,9 +63,9 @@ namespace Dapr.Actors.Description
Action action = () => ActorInterfaceDescription.Create(type);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The type 'System.ICloneable' is not an actor interface as it does not derive from the interface 'Dapr.Actors.IActor'.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The type 'System.ICloneable' is not an actor interface as it does not derive from the interface 'Dapr.Actors.IActor'.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -81,9 +78,9 @@ namespace Dapr.Actors.Description
Action action = () => ActorInterfaceDescription.Create(type);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The type '*+IClonableActor' is not an actor interface as it derive from a non actor interface 'System.ICloneable'. All actor interfaces must derive from 'Dapr.Actors.IActor'.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The type '.*\+IClonableActor' is not an actor interface as it derive from a non actor interface 'System.ICloneable'. All actor interfaces must derive from 'Dapr.Actors.IActor'.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -96,13 +93,12 @@ namespace Dapr.Actors.Description
var description = ActorInterfaceDescription.CreateUsingCRCId(type);
// Assert
description.Should().NotBeNull();
description.ShouldNotBeNull();
using var _ = new AssertionScope();
description.InterfaceType.Should().Be(type);
description.Id.Should().Be(-934188464);
description.V1Id.Should().NotBe(0);
description.Methods.Should().HaveCount(2);
description.InterfaceType.ShouldBe(type);
description.Id.ShouldBe(-934188464);
description.V1Id.ShouldNotBe(0);
description.Methods.Length.ShouldBe(2);
}
[Fact]
@ -115,9 +111,9 @@ namespace Dapr.Actors.Description
Action action = () => ActorInterfaceDescription.CreateUsingCRCId(type);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The type 'System.Object' is not an Actor interface as it is not an interface.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The type 'System.Object' is not an Actor interface as it is not an interface.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -130,9 +126,9 @@ namespace Dapr.Actors.Description
Action action = () => ActorInterfaceDescription.CreateUsingCRCId(type);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The type 'System.ICloneable' is not an actor interface as it does not derive from the interface 'Dapr.Actors.IActor'.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The type 'System.ICloneable' is not an actor interface as it does not derive from the interface 'Dapr.Actors.IActor'.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -145,9 +141,9 @@ namespace Dapr.Actors.Description
Action action = () => ActorInterfaceDescription.CreateUsingCRCId(type);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The type '*+IClonableActor' is not an actor interface as it derive from a non actor interface 'System.ICloneable'. All actor interfaces must derive from 'Dapr.Actors.IActor'.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The type '.*\+IClonableActor' is not an actor interface as it derive from a non actor interface 'System.ICloneable'. All actor interfaces must derive from 'Dapr.Actors.IActor'.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
internal interface IClonableActor : ICloneable, IActor

View File

@ -1,20 +1,20 @@
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Shouldly;
using Xunit;
namespace Dapr.Actors.Description
@ -31,13 +31,12 @@ namespace Dapr.Actors.Description
TestDescription description = new(type);
// Assert
description.Should().NotBeNull();
description.ShouldNotBeNull();
using var _ = new AssertionScope();
description.InterfaceType.Should().Be(type);
description.Id.Should().NotBe(0);
description.V1Id.Should().Be(0);
description.Methods.Should().BeEmpty();
description.InterfaceType.ShouldBe(type);
description.Id.ShouldNotBe(0);
description.V1Id.ShouldBe(0);
description.Methods.ShouldBeEmpty();
}
[Fact]
@ -50,9 +49,8 @@ namespace Dapr.Actors.Description
TestDescription description = new(type, useCRCIdGeneration: true);
// Assert
using var _ = new AssertionScope();
description.Id.Should().Be(-934188464);
description.V1Id.Should().NotBe(0);
description.Id.ShouldBe(-934188464);
description.V1Id.ShouldNotBe(0);
}
[Fact]
@ -65,9 +63,9 @@ namespace Dapr.Actors.Description
Action action = () => { TestDescription _ = new(type); };
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The actor interface '*+IGenericActor`1' is using generics. Generic interfaces cannot be remoted.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The actor interface '.*\+IGenericActor`1' is using generics. Generic interfaces cannot be remoted.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -80,9 +78,9 @@ namespace Dapr.Actors.Description
Action action = () => { TestDescription _ = new(type); };
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("The actor interface '*+IGenericActor`1[*IActor*]' is using generics. Generic interfaces cannot be remoted.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"The actor interface '.*\+IGenericActor`1\[.*IActor.*\]' is using generics. Generic interfaces cannot be remoted.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -95,12 +93,9 @@ namespace Dapr.Actors.Description
TestDescription description = new(type);
// Assert
using var _ = new AssertionScope();
description.Methods.Should().NotContainNulls();
description.Methods.Should().AllBeOfType<MethodDescription>();
description.Methods.Should().BeEquivalentTo(
new { Name = "GetInt" }
);
description.Methods.ShouldNotBeNull();
description.Methods.ShouldBeOfType<MethodDescription[]>();
description.Methods.Select(m => new {m.Name}).ShouldBe(new[] {new {Name = "GetInt"}});
}
[Fact]
@ -113,12 +108,9 @@ namespace Dapr.Actors.Description
TestDescription description = new(type, methodReturnCheck: MethodReturnCheck.EnsureReturnsVoid);
// Assert
using var _ = new AssertionScope();
description.Methods.Should().NotContainNulls();
description.Methods.Should().AllBeOfType<MethodDescription>();
description.Methods.Should().BeEquivalentTo(
new { Name = "GetString" },
new { Name = "MethodWithArguments" });
description.Methods.ShouldNotBeNull();
description.Methods.ShouldBeOfType<MethodDescription[]>();
description.Methods.Select(m => new {m.Name}).ShouldBe(new[] {new {Name = "GetString"}, new {Name="MethodWithArguments"}});
}
[Fact]
@ -128,12 +120,15 @@ namespace Dapr.Actors.Description
Type type = typeof(IVoidActor);
// Act
Action action = () => { TestDescription _ = new(type); };
Action action = () =>
{
TestDescription _ = new(type);
};
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'GetString' of actor interface '*+IVoidActor' does not return Task or Task<>. The actor interface methods must be async and must return either Task or Task<>.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'GetString' of actor interface '.*\+IVoidActor' does not return Task or Task<>. The actor interface methods must be async and must return either Task or Task<>.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -146,9 +141,9 @@ namespace Dapr.Actors.Description
Action action = () => { TestDescription _ = new(type, methodReturnCheck: MethodReturnCheck.EnsureReturnsVoid); };
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'GetString' of actor interface '*+IMethodActor' returns '*.Task`1[*System.String*]'*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'GetString' of actor interface '.*\+IMethodActor' returns '.*\.Task`1\[.*System.String.*\]'.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -161,9 +156,9 @@ namespace Dapr.Actors.Description
Action action = () => { TestDescription _ = new(type); };
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'GetString' of actor interface '*+IOverloadedMethodActor' is overloaded. The actor interface methods cannot be overloaded.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'GetString' of actor interface '.*\+IOverloadedMethodActor' is overloaded. The actor interface methods cannot be overloaded.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -176,9 +171,9 @@ namespace Dapr.Actors.Description
Action action = () => { TestDescription _ = new(type); };
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'Get' of actor interface '*+IGenericMethodActor' is using generics. The actor interface methods cannot use generics.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'Get' of actor interface '.*\+IGenericMethodActor' is using generics. The actor interface methods cannot use generics.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -191,9 +186,9 @@ namespace Dapr.Actors.Description
Action action = () => { TestDescription _ = new(type); };
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithVarArgs' of actor interface '*+IVariableActor' is using a variable argument list. The actor interface methods cannot have a variable argument list.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithVarArgs' of actor interface '.*\+IVariableActor' is using a variable argument list. The actor interface methods cannot have a variable argument list.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
internal interface ITestActor : IActor

View File

@ -1,21 +1,20 @@
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Reflection;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Shouldly;
using Xunit;
namespace Dapr.Actors.Description
@ -33,11 +32,10 @@ namespace Dapr.Actors.Description
var description = MethodArgumentDescription.Create("actor", methodInfo, parameterInfo);
// Assert
description.Should().NotBeNull();
description.ShouldNotBeNull();
using var _ = new AssertionScope();
description.Name.Should().Be("number");
description.ArgumentType.Should().Be<int>();
description.Name.ShouldBe("number");
description.ArgumentType.ShouldBe(typeof(int));
}
[Fact]
@ -52,9 +50,9 @@ namespace Dapr.Actors.Description
Action action = () => MethodArgumentDescription.Create("actor", methodInfo, parameterInfo);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithParams' of actor interface '*ITestActor' has variable length parameter 'values'. The actor interface methods must not have variable length parameters.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithParams' of actor interface '.*\+ITestActor' has variable length parameter 'values'. The actor interface methods must not have variable length parameters.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -69,9 +67,9 @@ namespace Dapr.Actors.Description
Action action = () => MethodArgumentDescription.Create("actor", methodInfo, parameterInfo);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithIn' of actor interface '*ITestActor' has out/ref/optional parameter 'value'. The actor interface methods must not have out, ref or optional parameters.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithIn' of actor interface '.*\+ITestActor' has out/ref/optional parameter 'value'. The actor interface methods must not have out, ref or optional parameters.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -86,9 +84,9 @@ namespace Dapr.Actors.Description
Action action = () => MethodArgumentDescription.Create("actor", methodInfo, parameterInfo);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithOut' of actor interface '*ITestActor' has out/ref/optional parameter 'value'. The actor interface methods must not have out, ref or optional parameters.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithOut' of actor interface '.*\+ITestActor' has out/ref/optional parameter 'value'. The actor interface methods must not have out, ref or optional parameters.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -103,9 +101,9 @@ namespace Dapr.Actors.Description
Action action = () => MethodArgumentDescription.Create("actor", methodInfo, parameterInfo);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithOptional' of actor interface '*ITestActor' has out/ref/optional parameter 'value'. The actor interface methods must not have out, ref or optional parameters.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithOptional' of actor interface '.*\+ITestActor' has out/ref/optional parameter 'value'. The actor interface methods must not have out, ref or optional parameters.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
internal interface ITestActor : IActor

View File

@ -1,22 +1,22 @@
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Shouldly;
using Xunit;
namespace Dapr.Actors.Description
@ -33,15 +33,14 @@ namespace Dapr.Actors.Description
var description = MethodDescription.Create("actor", methodInfo, false);
// Assert
description.Should().NotBeNull();
description.ShouldNotBeNull();
using var _ = new AssertionScope();
description.MethodInfo.Should().BeSameAs(methodInfo);
description.Name.Should().Be("GetString");
description.ReturnType.Should().Be<Task<string>>();
description.Id.Should().NotBe(0);
description.Arguments.Should().BeEmpty();
description.HasCancellationToken.Should().BeFalse();
description.MethodInfo.ShouldBeSameAs(methodInfo);
description.Name.ShouldBe("GetString");
description.ReturnType.ShouldBe(typeof(Task<string>));
description.Id.ShouldNotBe(0);
description.Arguments.ShouldBeEmpty();
description.HasCancellationToken.ShouldBeFalse();
}
[Fact]
@ -54,7 +53,7 @@ namespace Dapr.Actors.Description
var description = MethodDescription.Create("actor", methodInfo, true);
// Assert
description.Id.Should().Be(70257263);
description.Id.ShouldBe(70257263);
}
[Fact]
@ -67,13 +66,9 @@ namespace Dapr.Actors.Description
var description = MethodDescription.Create("actor", methodInfo, false);
// Assert
using var _ = new AssertionScope();
description.Arguments.Should().NotContainNulls();
description.Arguments.Should().AllBeOfType<MethodArgumentDescription>();
description.Arguments.Should().BeEquivalentTo(
new { Name = "number" },
new { Name = "choice" },
new { Name = "information" });
description.Arguments.ShouldNotBeNull();
description.Arguments.ShouldBeOfType<MethodArgumentDescription[]>();
description.Arguments.Select(m => new {m.Name}).ShouldBe(new[] {new {Name = "number"}, new {Name = "choice"}, new {Name = "information"}});
}
[Fact]
@ -86,7 +81,7 @@ namespace Dapr.Actors.Description
var description = MethodDescription.Create("actor", methodInfo, false);
// Assert
description.HasCancellationToken.Should().BeTrue();
description.HasCancellationToken.ShouldBeTrue();
}
[Fact]
@ -100,9 +95,9 @@ namespace Dapr.Actors.Description
Action action = () => MethodDescription.Create("actor", methodInfo, false);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithTokenNotLast' of actor interface '*+ITestActor' has a '*.CancellationToken' parameter that is not the last parameter. If an actor method accepts a '*.CancellationToken' parameter, it must be the last parameter.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithTokenNotLast' of actor interface '.*\+ITestActor' has a '.*\.CancellationToken' parameter that is not the last parameter. If an actor method accepts a '.*\.CancellationToken' parameter, it must be the last parameter\..*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
[Fact]
@ -116,9 +111,9 @@ namespace Dapr.Actors.Description
Action action = () => MethodDescription.Create("actor", methodInfo, false);
// Assert
action.Should().ThrowExactly<ArgumentException>()
.WithMessage("Method 'MethodWithMultipleTokens' of actor interface '*+ITestActor' has a '*.CancellationToken' parameter that is not the last parameter. If an actor method accepts a '*.CancellationToken' parameter, it must be the last parameter.*")
.And.ParamName.Should().Be("actorInterfaceType");
var exception = Should.Throw<ArgumentException>(action);
exception.Message.ShouldMatch(@"Method 'MethodWithMultipleTokens' of actor interface '.*\+ITestActor' has a '.*\.CancellationToken' parameter that is not the last parameter. If an actor method accepts a '.*\.CancellationToken' parameter, it must be the last parameter.*");
exception.ParamName.ShouldBe("actorInterfaceType");
}
internal interface ITestActor : IActor

View File

@ -0,0 +1,90 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using Xunit;
namespace Dapr.Actors.Extensions;
public sealed class DurationExtensionsTests
{
[Theory]
[InlineData("@yearly", 364, 0, 0, 0, 0)]
[InlineData("@monthly", 28, 0, 0, 0, 0 )]
[InlineData("@weekly", 7, 0, 0, 0, 0 )]
[InlineData("@daily", 1, 0, 0, 0, 0)]
[InlineData("@midnight", 0, 0, 0, 0, 0 )]
[InlineData("@hourly", 0, 1, 0, 0, 0)]
[InlineData("@every 1h", 0, 1, 0, 0, 0)]
[InlineData("@every 30m", 0, 0, 30, 0, 0)]
[InlineData("@every 45s", 0, 0, 0, 45, 0)]
[InlineData("@every 1.5h", 0, 1, 30, 0, 0)]
[InlineData("@every 1h30m", 0, 1, 30, 0, 0)]
[InlineData("@every 1h30m45s", 0, 1, 30, 45, 0)]
[InlineData("@every 1h30m45.3s", 0, 1, 30, 45, 300)]
[InlineData("@every 100ms", 0, 0, 0, 0, 100)]
[InlineData("@every 1s500ms", 0, 0, 0, 1, 500)]
[InlineData("@every 1m1s", 0, 0, 1, 1, 0)]
[InlineData("@every 1.1m", 0, 0, 1, 6, 0)]
[InlineData("@every 1.5h30m45s100ms", 0, 2, 0, 45, 100)]
public void ValidatePrefixedPeriodParsing(string input, int expectedDays, int expectedHours, int expectedMinutes, int expectedSeconds, int expectedMilliseconds)
{
var result = input.FromPrefixedPeriod();
if (input is "@yearly" or "@monthly")
{
Assert.True(result.Days >= expectedDays);
return;
}
Assert.Equal(expectedDays, result.Days);
Assert.Equal(expectedHours, result.Hours);
Assert.Equal(expectedMinutes, result.Minutes);
Assert.Equal(expectedSeconds, result.Seconds);
Assert.Equal(expectedMilliseconds, result.Milliseconds);
}
[Theory]
[InlineData("@yearly", true)]
[InlineData("@monthly", true)]
[InlineData("@weekly", true)]
[InlineData("@daily", true)]
[InlineData("@midnight", true)]
[InlineData("@hourly", true)]
[InlineData("@every 1h", true)]
[InlineData("@every 30m", true)]
[InlineData("@every 45s", true)]
[InlineData("@every 1.5h", true)]
[InlineData("@every 1h30m", true)]
[InlineData("@every 1h30m45s", true)]
[InlineData("@every 1h30m45.3s", true)]
[InlineData("@every 100ms", true)]
[InlineData("@every 1s500ms", true)]
[InlineData("@every 1m1s", true)]
[InlineData("@every 1.1m", true)]
[InlineData("@every 1.5h30m45s100ms", true)]
public void TestIsDurationExpression(string input, bool expectedResult)
{
var actualResult = input.IsDurationExpression();
Assert.Equal(expectedResult, actualResult);
}
[Fact]
public void ValidateExceptionForUnknownExpression()
{
Assert.Throws<ArgumentException>(() =>
{
var result = "every 100s".FromPrefixedPeriod();
});
}
}

View File

@ -0,0 +1,41 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Xunit;
namespace Dapr.Actors.Extensions;
public sealed class StringExtensionsTests
{
[Fact]
public void ValidateMatchesValue()
{
var matchingValues = new List<string> { "apples", "bananas", "cherries", };
const string value = "I have four cherries";
var result = value.EndsWithAny(matchingValues, StringComparison.InvariantCulture);
Assert.True(result);
}
[Fact]
public void ValidateDoesNotMatchValue()
{
var matchingValues = new List<string> { "apples", "bananas", "cherries", };
const string value = "I have four grapes";
var result = value.EndsWithAny(matchingValues, StringComparison.InvariantCulture);
Assert.False(result);
}
}

View File

@ -11,118 +11,117 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Test
namespace Dapr.Actors.Test;
using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Runtime;
/// <summary>
/// Interface for test actor.
/// </summary>
public interface ITestActor : IActor
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Runtime;
/// <summary>
/// GetCount method for TestActor.
/// </summary>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The current count as stored in actor.</returns>
Task<int> GetCountAsync(CancellationToken cancellationToken);
/// <summary>
/// Interface for test actor.
/// SetCount method for test actor.
/// </summary>
public interface ITestActor : IActor
{
/// <summary>
/// GetCount method for TestActor.
/// </summary>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The current count as stored in actor.</returns>
Task<int> GetCountAsync(CancellationToken cancellationToken);
/// <param name="count">Count to set for the actor.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>Task.</returns>
Task SetCountAsync(int count, CancellationToken cancellationToken);
}
/// <summary>
/// SetCount method for test actor.
/// </summary>
/// <param name="count">Count to set for the actor.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>Task.</returns>
Task SetCountAsync(int count, CancellationToken cancellationToken);
/// <summary>
/// Test Actor Class.
/// </summary>
public class TestActor : Actor, ITestActor
{
public TestActor(ActorHost host, IActorStateManager stateManager = null)
: base(host)
{
if (stateManager != null)
{
this.StateManager = stateManager;
}
}
/// <summary>
/// Test Actor Class.
/// </summary>
public class TestActor : Actor, ITestActor
/// <inheritdoc/>
public Task<int> GetCountAsync(CancellationToken cancellationToken)
{
public TestActor(ActorHost host, IActorStateManager stateManager = null)
: base(host)
{
if (stateManager != null)
{
this.StateManager = stateManager;
}
}
return Task.FromResult(5);
}
/// <inheritdoc/>
public Task<int> GetCountAsync(CancellationToken cancellationToken)
{
return Task.FromResult(5);
}
/// <inheritdoc/>
public Task SetCountAsync(int count, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task SetCountAsync(int count, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task SaveTestState()
{
return this.SaveStateAsync();
}
public Task SaveTestState()
{
return this.SaveStateAsync();
}
public Task ResetTestStateAsync()
{
return this.ResetStateAsync();
}
public Task ResetTestStateAsync()
{
return this.ResetStateAsync();
}
public void TimerCallbackNonTaskReturnType()
{
}
public void TimerCallbackNonTaskReturnType()
{
}
public Task TimerCallbackTwoArguments(int i, int j)
{
Console.WriteLine(i + j);
return default;
}
public Task TimerCallbackTwoArguments(int i, int j)
{
Console.WriteLine(i + j);
return default;
}
private Task TimerCallbackPrivate()
{
return default;
}
private Task TimerCallbackPrivate()
{
return default;
}
protected Task TimerCallbackProtected()
{
return default;
}
protected Task TimerCallbackProtected()
{
return default;
}
internal Task TimerCallbackInternal()
{
return default;
}
internal Task TimerCallbackInternal()
{
return default;
}
public Task TimerCallbackPublicWithNoArguments()
{
return default;
}
public Task TimerCallbackPublicWithNoArguments()
{
return default;
}
public Task TimerCallbackPublicWithOneArgument(int i)
{
return default;
}
public Task TimerCallbackPublicWithOneArgument(int i)
{
return default;
}
public Task TimerCallbackOverloaded()
{
return default;
}
public Task TimerCallbackOverloaded()
{
return default;
}
public Task TimerCallbackOverloaded(int i)
{
return default;
}
public Task TimerCallbackOverloaded(int i)
{
return default;
}
public static Task TimerCallbackStatic()
{
return default;
}
public static Task TimerCallbackStatic()
{
return default;
}
}

View File

@ -12,6 +12,8 @@
// ------------------------------------------------------------------------
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Client;
@ -175,6 +177,160 @@ namespace Dapr.Actors.Runtime
Assert.Equal(1, activator.DeleteCallCount);
}
[Fact]
public async Task DeserializeTimer_Period_Iso8601_Time()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"0h0m7s10ms\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Every()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@every 15s\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromSeconds(15), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Every2()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@every 3h2m15s\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromHours(3).Add(TimeSpan.FromMinutes(2)).Add(TimeSpan.FromSeconds(15)), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Monthly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@monthly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromDays(30), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Weekly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@weekly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromDays(7), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Daily()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@daily\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromDays(1), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Hourly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@hourly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromHours(1), result.Period);
}
[Fact]
public async Task DeserializeTimer_DueTime_DaprFormat_Hourly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"dueTime\": \"@hourly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.FromHours(1), result.DueTime);
Assert.Equal(TimeSpan.Zero, result.Period);
}
[Fact]
public async Task DeserializeTimer_DueTime_Iso8601Times()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"dueTime\": \"0h0m7s10ms\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.Period);
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.DueTime);
}
[Fact]
public async Task DeserializeTimer_Ttl_DaprFormat_Hourly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"ttl\": \"@hourly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.Zero, result.Period);
Assert.Equal(TimeSpan.FromHours(1), result.Ttl);
}
[Fact]
public async Task DeserializeTimer_Ttl_Iso8601Times()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"ttl\": \"0h0m7s10ms\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.Zero, result.Period);
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.Ttl);
}
private interface ITestActor : IActor { }
private class TestActor : Actor, ITestActor, IDisposable

View File

@ -17,7 +17,7 @@ namespace Dapr.Actors.Test.Runtime
using Moq;
using Xunit;
using System;
using FluentAssertions;
using Shouldly;
public sealed class ActorRuntimeOptionsTests
{
@ -59,7 +59,7 @@ namespace Dapr.Actors.Test.Runtime
var options = new ActorRuntimeOptions();
Action action = () => options.ActorIdleTimeout = TimeSpan.FromSeconds(-1);
action.Should().Throw<ArgumentOutOfRangeException>();
action.ShouldThrow<ArgumentOutOfRangeException>();
}
@ -78,7 +78,7 @@ namespace Dapr.Actors.Test.Runtime
var options = new ActorRuntimeOptions();
Action action = () => options.ActorScanInterval = TimeSpan.FromSeconds(-1);
action.Should().Throw<ArgumentOutOfRangeException>();
action.ShouldThrow<ArgumentOutOfRangeException>();
}
[Fact]
@ -96,7 +96,7 @@ namespace Dapr.Actors.Test.Runtime
var options = new ActorRuntimeOptions();
Action action = () => options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(-1);
action.Should().Throw<ArgumentOutOfRangeException>();
action.ShouldThrow<ArgumentOutOfRangeException>();
}
[Fact]
@ -115,7 +115,7 @@ namespace Dapr.Actors.Test.Runtime
var options = new ActorRuntimeOptions();
Action action = () => options.JsonSerializerOptions = null;
action.Should().Throw<ArgumentNullException>();
action.ShouldThrow<ArgumentNullException>();
}
[Fact]
@ -124,7 +124,7 @@ namespace Dapr.Actors.Test.Runtime
var options = new ActorRuntimeOptions();
Action action = () => options.RemindersStoragePartitions = -1;
action.Should().Throw<ArgumentOutOfRangeException>();
action.ShouldThrow<ArgumentOutOfRangeException>();
}
[Fact]

View File

@ -11,119 +11,112 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace Dapr.Actors.Test.Runtime
namespace Dapr.Actors.Test.Runtime;
using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Runtime;
using Shouldly;
using Moq;
using Xunit;
public sealed class ActorTests
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Runtime;
using FluentAssertions;
using Moq;
using Xunit;
public sealed class ActorTests
[Fact]
public void TestNewActorWithMockStateManager()
{
[Fact]
public void TestNewActorWithMockStateManager()
{
var mockStateManager = new Mock<IActorStateManager>();
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
testDemoActor.Host.Should().NotBeNull();
testDemoActor.Id.Should().NotBeNull();
}
[Fact]
public async Task TestSaveState()
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.SaveStateAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
await testDemoActor.SaveTestState();
mockStateManager.Verify(manager => manager.SaveStateAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task TestResetStateAsync()
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
await testDemoActor.ResetTestStateAsync();
mockStateManager.Verify(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Theory]
[InlineData("NonExistentMethod", "Timer callback method: NonExistentMethod does not exist in the Actor class: TestActor")]
[InlineData("TimerCallbackTwoArguments", "Timer callback can accept only zero or one parameters")]
[InlineData("TimerCallbackNonTaskReturnType", "Timer callback can only return type Task")]
[InlineData("TimerCallbackOverloaded", "Timer callback method: TimerCallbackOverloaded cannot be overloaded.")]
public void ValidateTimerCallback_CallbackMethodDoesNotMeetRequirements(string callback, string expectedErrorMessage)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
;
FluentActions.Invoking(() =>
testDemoActor.ValidateTimerCallback(testDemoActor.Host, callback))
.Should().Throw<ArgumentException>()
.WithMessage(expectedErrorMessage);
}
[Theory]
[InlineData("TimerCallbackPrivate")]
[InlineData("TimerCallbackProtected")]
[InlineData("TimerCallbackInternal")]
[InlineData("TimerCallbackPublicWithNoArguments")]
[InlineData("TimerCallbackPublicWithOneArgument")]
[InlineData("TimerCallbackStatic")]
public void ValidateTimerCallback_CallbackMethodMeetsRequirements(string callback)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
;
FluentActions.Invoking(() =>
testDemoActor.ValidateTimerCallback(testDemoActor.Host, callback))
.Should().NotThrow();
}
[Theory]
[InlineData("TimerCallbackPrivate")]
[InlineData("TimerCallbackPublicWithOneArgument")]
[InlineData("TimerCallbackStatic")]
public void GetMethodInfoUsingReflection_MethodsMatchingBindingFlags(string callback)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
var methodInfo = testDemoActor.GetMethodInfoUsingReflection(testDemoActor.Host.ActorTypeInfo.ImplementationType, callback);
Assert.NotNull(methodInfo);
}
[Theory]
[InlineData("TestActor")] // Constructor
public void GetMethodInfoUsingReflection_MethodsNotMatchingBindingFlags(string callback)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
var methodInfo = testDemoActor.GetMethodInfoUsingReflection(testDemoActor.Host.ActorTypeInfo.ImplementationType, callback);
Assert.Null(methodInfo);
}
/// <summary>
/// On my test code I want to pass the mock statemanager all the time.
/// </summary>
/// <param name="actorStateManager">Mock StateManager.</param>
/// <returns>TestActor.</returns>
private TestActor CreateTestDemoActor(IActorStateManager actorStateManager)
{
var host = ActorHost.CreateForTest<TestActor>();
var testActor = new TestActor(host, actorStateManager);
return testActor;
}
var mockStateManager = new Mock<IActorStateManager>();
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
testDemoActor.Host.ShouldNotBeNull();
testDemoActor.Id.ShouldNotBeNull();
}
[Fact]
public async Task TestSaveState()
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.SaveStateAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
await testDemoActor.SaveTestState();
mockStateManager.Verify(manager => manager.SaveStateAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task TestResetStateAsync()
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
await testDemoActor.ResetTestStateAsync();
mockStateManager.Verify(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Theory]
[InlineData("NonExistentMethod", "Timer callback method: NonExistentMethod does not exist in the Actor class: TestActor")]
[InlineData("TimerCallbackTwoArguments", "Timer callback can accept only zero or one parameters")]
[InlineData("TimerCallbackNonTaskReturnType", "Timer callback can only return type Task")]
[InlineData("TimerCallbackOverloaded", "Timer callback method: TimerCallbackOverloaded cannot be overloaded.")]
public void ValidateTimerCallback_CallbackMethodDoesNotMeetRequirements(string callback, string expectedErrorMessage)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
Should.Throw<ArgumentException>(() => testDemoActor.ValidateTimerCallback(testDemoActor.Host, callback))
.Message.ShouldBe(expectedErrorMessage);
}
[Theory]
[InlineData("TimerCallbackPrivate")]
[InlineData("TimerCallbackProtected")]
[InlineData("TimerCallbackInternal")]
[InlineData("TimerCallbackPublicWithNoArguments")]
[InlineData("TimerCallbackPublicWithOneArgument")]
[InlineData("TimerCallbackStatic")]
public void ValidateTimerCallback_CallbackMethodMeetsRequirements(string callback)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
Should.NotThrow(() => testDemoActor.ValidateTimerCallback(testDemoActor.Host, callback));
}
[Theory]
[InlineData("TimerCallbackPrivate")]
[InlineData("TimerCallbackPublicWithOneArgument")]
[InlineData("TimerCallbackStatic")]
public void GetMethodInfoUsingReflection_MethodsMatchingBindingFlags(string callback)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
var methodInfo = testDemoActor.GetMethodInfoUsingReflection(testDemoActor.Host.ActorTypeInfo.ImplementationType, callback);
Assert.NotNull(methodInfo);
}
[Theory]
[InlineData("TestActor")] // Constructor
public void GetMethodInfoUsingReflection_MethodsNotMatchingBindingFlags(string callback)
{
var mockStateManager = new Mock<IActorStateManager>();
mockStateManager.Setup(manager => manager.ClearCacheAsync(It.IsAny<CancellationToken>()));
var testDemoActor = this.CreateTestDemoActor(mockStateManager.Object);
var methodInfo = testDemoActor.GetMethodInfoUsingReflection(testDemoActor.Host.ActorTypeInfo.ImplementationType, callback);
Assert.Null(methodInfo);
}
/// <summary>
/// On my test code I want to pass the mock statemanager all the time.
/// </summary>
/// <param name="actorStateManager">Mock StateManager.</param>
/// <returns>TestActor.</returns>
private TestActor CreateTestDemoActor(IActorStateManager actorStateManager)
{
var host = ActorHost.CreateForTest<TestActor>();
var testActor = new TestActor(host, actorStateManager);
return testActor;
}
}

View File

@ -1,9 +1,24 @@
// ------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -11,78 +26,156 @@ using Moq;
using Xunit;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Dapr.Actors.Runtime
namespace Dapr.Actors.Runtime;
public sealed class DefaultActorTimerManagerTests
{
public sealed class DefaultActorTimerManagerTests
/// <summary>
/// When register reminder is called, interactor is called with correct data.
/// </summary>
/// <returns></returns>
[Fact]
public async Task RegisterReminderAsync_CallsInteractor_WithCorrectData()
{
/// <summary>
/// When register reminder is called, interactor is called with correct data.
/// </summary>
/// <returns></returns>
[Fact]
public async Task RegisterReminderAsync_CallsInteractor_WithCorrectData()
var actorId = "123";
var actorType = "abc";
var interactor = new Mock<TestDaprInteractor>();
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
var actorReminder = new ActorReminder(actorType, new ActorId(actorId), "remindername", Array.Empty<byte>(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
var actualData = string.Empty;
interactor
.Setup(d => d.RegisterReminderAsync(actorType, actorId, "remindername", It.Is<string>(data => !string.IsNullOrEmpty(data)), It.IsAny<CancellationToken>()))
.Callback<string, string, string, string, CancellationToken>((innerType, innerId, reminderName, data, token) => {
actualData = data;
})
.Returns(Task.CompletedTask);
await defaultActorTimerManager.RegisterReminderAsync(actorReminder);
JsonElement json = JsonSerializer.Deserialize<dynamic>(actualData);
var isPeriodSet = json.TryGetProperty("period", out var period);
var isdDueTimeSet = json.TryGetProperty("dueTime", out var dueTime);
Assert.True(isPeriodSet);
Assert.True(isdDueTimeSet);
Assert.Equal("0h1m0s0ms", period.GetString());
Assert.Equal("0h1m0s0ms", dueTime.GetString());
}
/// <summary>
/// When register reminder is called with repetition, interactor is called with correct data.
/// </summary>
/// <returns></returns>
[Fact]
public async Task RegisterReminderAsync_WithRepetition_CallsInteractor_WithCorrectData()
{
const string actorId = "123";
const string actorType = "abc";
var interactor = new Mock<TestDaprInteractor>();
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
var actorReminder = new ActorReminder(actorType, new ActorId(actorId), "remindername", new byte[] { }, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), 10);
var actualData = string.Empty;
interactor
.Setup(d => d.RegisterReminderAsync(actorType, actorId, "remindername", It.Is<string>(data => !string.IsNullOrEmpty(data)), It.IsAny<CancellationToken>()))
.Callback<string, string, string, string, CancellationToken>((innerType, innerActorId, reminderName, data, token) => {
actualData = data;
})
.Returns(Task.CompletedTask);
await defaultActorTimerManager.RegisterReminderAsync(actorReminder);
JsonElement json = JsonSerializer.Deserialize<dynamic>(actualData);
var isPeriodSet = json.TryGetProperty("period", out var period);
var isdDueTimeSet = json.TryGetProperty("dueTime", out var dueTime);
Assert.True(isPeriodSet);
Assert.True(isdDueTimeSet);
Assert.Equal("R10/PT1M", period.GetString());
Assert.Equal("0h1m0s0ms", dueTime.GetString());
}
/// <summary>
/// Get the GetReminder method is called without a registered reminder, it should return null.
/// </summary>
[Fact]
public async Task GetReminderAsync_ReturnsNullWhenUnavailable()
{
const string actorId = "123";
const string actorType = "abc";
const string reminderName = "reminderName";
var interactor = new Mock<TestDaprInteractor>();
interactor
.Setup(d => d.GetReminderAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
var reminderResult = await defaultActorTimerManager.GetReminderAsync(new ActorReminderToken(actorType, new ActorId(actorId), reminderName));
Assert.Null(reminderResult);
}
[Fact]
public async Task GetReminderAsync_ReturnsNullWhenDeserialziationFails()
{
const string actorId = "123";
const string actorType = "abc";
const string reminderName = "reminderName";
var interactor = new Mock<TestDaprInteractor>();
var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}") };
interactor
.Setup(d => d.GetReminderAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
var reminderResult = await defaultActorTimerManager.GetReminderAsync(new ActorReminderToken(actorType, new ActorId(actorId), reminderName));
Assert.Null(reminderResult);
}
[Fact]
public async Task GetReminderAsync_ReturnsResultWhenAvailable()
{
const string actorId = "123";
const string actorType = "abc";
const string reminderName = "reminderName";
var interactor = new Mock<TestDaprInteractor>();
//Create the reminder we'll return
var state = Array.Empty<byte>();
var dueTime = TimeSpan.FromMinutes(1);
var period = TimeSpan.FromMinutes(1);
var actorReminder = new ActorReminder(actorType, new ActorId(actorId), "remindername", state, dueTime, period, 10);
//Serialize and create the response value
var actorReminderInfo = new ReminderInfo(actorReminder.State, actorReminder.DueTime, actorReminder.Period,
actorReminder.Repetitions, actorReminder.Ttl);
var serializedActorReminderInfo = await actorReminderInfo.SerializeAsync();
var reminderResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
var actorId = "123";
var actorType = "abc";
var interactor = new Mock<TestDaprInteractor>();
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
var actorReminder = new ActorReminder(actorType, new ActorId(actorId), "remindername", new byte[] { }, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
var actualData = string.Empty;
Content = new StringContent(serializedActorReminderInfo)
};
interactor
.Setup(d => d.RegisterReminderAsync(actorType, actorId, "remindername", It.Is<string>(data => !string.IsNullOrEmpty(data)), It.IsAny<CancellationToken>()))
.Callback<string, string, string, string, CancellationToken>((actorType, actorID, reminderName, data, token) => {
actualData = data;
})
.Returns(Task.CompletedTask);
await defaultActorTimerManager.RegisterReminderAsync(actorReminder);
JsonElement json = JsonSerializer.Deserialize<dynamic>(actualData);
var isPeriodSet = json.TryGetProperty("period", out var period);
var isdDueTimeSet = json.TryGetProperty("dueTime", out var dueTime);
//Register the response
interactor
.Setup(d => d.GetReminderAsync(actorType, actorId, reminderName, It.IsAny<CancellationToken>()))
.Callback<string, string, string, CancellationToken>((type, id, name, token) => {
})
.Returns(Task.FromResult(reminderResponse));
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
Assert.True(isPeriodSet);
Assert.True(isdDueTimeSet);
var reminderResult = await defaultActorTimerManager.GetReminderAsync(new ActorReminderToken(actorType, new ActorId(actorId), reminderName));
Assert.NotNull(reminderResult);
Assert.Equal("0h1m0s0ms", period.GetString());
Assert.Equal("0h1m0s0ms", dueTime.GetString());
}
/// <summary>
/// When register reminder is called with repetition, interactor is called with correct data.
/// </summary>
/// <returns></returns>
[Fact]
public async Task RegisterReminderAsync_WithRepetition_CallsInteractor_WithCorrectData()
{
var actorId = "123";
var actorType = "abc";
var interactor = new Mock<TestDaprInteractor>();
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
var actorReminder = new ActorReminder(actorType, new ActorId(actorId), "remindername", new byte[] { }, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), 10);
var actualData = string.Empty;
interactor
.Setup(d => d.RegisterReminderAsync(actorType, actorId, "remindername", It.Is<string>(data => !string.IsNullOrEmpty(data)), It.IsAny<CancellationToken>()))
.Callback<string, string, string, string, CancellationToken>((actorType, actorID, reminderName, data, token) => {
actualData = data;
})
.Returns(Task.CompletedTask);
await defaultActorTimerManager.RegisterReminderAsync(actorReminder);
JsonElement json = JsonSerializer.Deserialize<dynamic>(actualData);
var isPeriodSet = json.TryGetProperty("period", out var period);
var isdDueTimeSet = json.TryGetProperty("dueTime", out var dueTime);
Assert.True(isPeriodSet);
Assert.True(isdDueTimeSet);
Assert.Equal("R10/PT1M", period.GetString());
Assert.Equal("0h1m0s0ms", dueTime.GetString());
}
Assert.Equal(dueTime, reminderResult.DueTime);
Assert.Equal(state, reminderResult.State);
Assert.Equal(period, reminderResult.Period);
Assert.Equal(reminderName, reminderResult.Name);
}
}

View File

@ -14,69 +14,68 @@
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using FluentAssertions;
using Shouldly;
using Xunit;
namespace Dapr.Actors.Serialization
namespace Dapr.Actors.Serialization;
public class ActorReferenceDataContractSerializationTest
{
public class ActorReferenceDataContractSerializationTest
[Fact]
public void CanSerializeAndDeserializeActorId()
{
[Fact]
public void CanSerializeAndDeserializeActorId()
var id = ActorId.CreateRandom();
DataContractSerializer serializer = new DataContractSerializer(id.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, id);
ms.Position = 0;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ms);
xmlDoc.DocumentElement.Name.ShouldBe("ActorId");
xmlDoc.DocumentElement.InnerText.ShouldBe(id.ToString());
ms.Position = 0;
var deserializedActorId = serializer.ReadObject(ms) as ActorId;
deserializedActorId.ShouldBe(id);
}
[Fact]
public void CanSerializeAndDeserializeNullActorId()
{
ActorId id = null;
DataContractSerializer serializer = new DataContractSerializer(typeof(ActorId));
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, id);
ms.Position = 0;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ms);
xmlDoc.DocumentElement.Name.ShouldBe("ActorId");
xmlDoc.DocumentElement.InnerText.ShouldBe(string.Empty);
ms.Position = 0;
var deserializedActorId = serializer.ReadObject(ms) as ActorId;
deserializedActorId.ShouldBe(id);
}
[Fact]
public void CanRoundTripActorReference()
{
var actorId = new ActorId("abc");
var actorReference = new ActorReference()
{
var id = ActorId.CreateRandom();
DataContractSerializer serializer = new DataContractSerializer(id.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, id);
ms.Position = 0;
ActorId = actorId,
ActorType = "TestActor"
};
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ms);
xmlDoc.DocumentElement.Name.Should().Be("ActorId");
xmlDoc.DocumentElement.InnerText.Should().Be(id.ToString());
ms.Position = 0;
DataContractSerializer serializer = new DataContractSerializer(actorReference.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, actorReference);
ms.Position = 0;
var deserializedActorId = serializer.ReadObject(ms) as ActorId;
deserializedActorId.Should().Be(id);
}
[Fact]
public void CanSerializeAndDeserializeNullActorId()
{
ActorId id = null;
DataContractSerializer serializer = new DataContractSerializer(typeof(ActorId));
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, id);
ms.Position = 0;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ms);
xmlDoc.DocumentElement.Name.Should().Be("ActorId");
xmlDoc.DocumentElement.InnerText.Should().Be(string.Empty);
ms.Position = 0;
var deserializedActorId = serializer.ReadObject(ms) as ActorId;
deserializedActorId.Should().Be(id);
}
[Fact]
public void CanRoundTripActorReference()
{
var actorId = new ActorId("abc");
var actorReference = new ActorReference()
{
ActorId = actorId,
ActorType = "TestActor"
};
DataContractSerializer serializer = new DataContractSerializer(actorReference.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, actorReference);
ms.Position = 0;
var deserializedActorRef = serializer.ReadObject(ms) as ActorReference;
deserializedActorRef.ActorId.Should().Be(actorId);
deserializedActorRef.ActorType.Should().Be("TestActor");
}
var deserializedActorRef = serializer.ReadObject(ms) as ActorReference;
deserializedActorRef.ActorId.ShouldBe(actorId);
deserializedActorRef.ActorType.ShouldBe("TestActor");
}
}

View File

@ -15,101 +15,100 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Xunit;
namespace Dapr.Actors.Serialization
namespace Dapr.Actors.Serialization;
public class ActorIdJsonConverterTest
{
public class ActorIdJsonConverterTest
[Fact]
public void CanSerializeActorId()
{
[Fact]
public void CanSerializeActorId()
{
var id = ActorId.CreateRandom();
var document = new { actor = id, };
var id = ActorId.CreateRandom();
var document = new { actor = id, };
// We use strings for ActorId - the result should be the same as passing the Id directly.
var expected = JsonSerializer.Serialize(new { actor = id.GetId(), });
// We use strings for ActorId - the result should be the same as passing the Id directly.
var expected = JsonSerializer.Serialize(new { actor = id.GetId(), });
var serialized = JsonSerializer.Serialize(document);
var serialized = JsonSerializer.Serialize(document);
Assert.Equal(expected, serialized);
}
Assert.Equal(expected, serialized);
}
[Fact]
public void CanSerializeNullActorId()
{
var document = new { actor = (ActorId)null, };
[Fact]
public void CanSerializeNullActorId()
{
var document = new { actor = (ActorId)null, };
var expected = JsonSerializer.Serialize(new { actor = (string)null, });
var expected = JsonSerializer.Serialize(new { actor = (string)null, });
var serialized = JsonSerializer.Serialize(document);
var serialized = JsonSerializer.Serialize(document);
Assert.Equal(expected, serialized);
}
Assert.Equal(expected, serialized);
}
[Fact]
public void CanDeserializeActorId()
{
var id = ActorId.CreateRandom().GetId();
var document = $@"
[Fact]
public void CanDeserializeActorId()
{
var id = ActorId.CreateRandom().GetId();
var document = $@"
{{
""actor"": ""{id}""
}}";
var deserialized = JsonSerializer.Deserialize<ActorHolder>(document);
var deserialized = JsonSerializer.Deserialize<ActorHolder>(document);
Assert.Equal(id, deserialized.Actor.GetId());
}
Assert.Equal(id, deserialized.Actor.GetId());
}
[Fact]
public void CanDeserializeNullActorId()
{
const string document = @"
[Fact]
public void CanDeserializeNullActorId()
{
const string document = @"
{
""actor"": null
}";
var deserialized = JsonSerializer.Deserialize<ActorHolder>(document);
var deserialized = JsonSerializer.Deserialize<ActorHolder>(document);
Assert.Null(deserialized.Actor);
}
Assert.Null(deserialized.Actor);
}
[Theory]
[InlineData("{ \"actor\": ")]
[InlineData("{ \"actor\": \"hi")]
[InlineData("{ \"actor\": }")]
[InlineData("{ \"actor\": 3 }")]
[InlineData("{ \"actor\": \"\"}")]
[InlineData("{ \"actor\": \" \"}")]
public void CanReportErrorsFromInvalidData(string document)
[Theory]
[InlineData("{ \"actor\": ")]
[InlineData("{ \"actor\": \"hi")]
[InlineData("{ \"actor\": }")]
[InlineData("{ \"actor\": 3 }")]
[InlineData("{ \"actor\": \"\"}")]
[InlineData("{ \"actor\": \" \"}")]
public void CanReportErrorsFromInvalidData(string document)
{
// The error messages are provided by the serializer, don't test them here
// that would be fragile.
Assert.Throws<JsonException>(() =>
{
// The error messages are provided by the serializer, don't test them here
// that would be fragile.
Assert.Throws<JsonException>(() =>
{
JsonSerializer.Deserialize<ActorHolder>(document);
});
}
JsonSerializer.Deserialize<ActorHolder>(document);
});
}
// Regression test for #444
[Fact]
public void CanRoundTripActorReference()
// Regression test for #444
[Fact]
public void CanRoundTripActorReference()
{
var reference = new ActorReference()
{
var reference = new ActorReference()
{
ActorId = ActorId.CreateRandom(),
ActorType = "TestActor",
};
ActorId = ActorId.CreateRandom(),
ActorType = "TestActor",
};
var serialized = JsonSerializer.Serialize(reference);
var deserialized = JsonSerializer.Deserialize<ActorReference>(serialized);
var serialized = JsonSerializer.Serialize(reference);
var deserialized = JsonSerializer.Deserialize<ActorReference>(serialized);
Assert.Equal(reference.ActorId.GetId(), deserialized.ActorId.GetId());
Assert.Equal(reference.ActorType, deserialized.ActorType);
}
Assert.Equal(reference.ActorId.GetId(), deserialized.ActorId.GetId());
Assert.Equal(reference.ActorType, deserialized.ActorType);
}
private class ActorHolder
{
[JsonPropertyName("actor")]
public ActorId Actor { get; set; }
}
private class ActorHolder
{
[JsonPropertyName("actor")]
public ActorId Actor { get; set; }
}
}

View File

@ -16,30 +16,29 @@ using System.Text.Json;
using Xunit;
#pragma warning disable 0618
namespace Dapr.Actors.Runtime
namespace Dapr.Actors.Runtime;
public class TimerInfoJsonConverterTest
{
public class TimerInfoJsonConverterTest
[Theory]
[InlineData("test", new byte[] {1, 2, 3}, 2, 4)]
[InlineData(null, new byte[] {1}, 2, 4)]
[InlineData("test", null, 2, 4)]
[InlineData("test", new byte[] {1}, 0, 4)]
[InlineData("test", new byte[] {1}, 2, 0)]
public void CanSerializeAndDeserializeTimerInfo(string callback, byte[] state, int dueTime, int period)
{
[Theory]
[InlineData("test", new byte[] {1, 2, 3}, 2, 4)]
[InlineData(null, new byte[] {1}, 2, 4)]
[InlineData("test", null, 2, 4)]
[InlineData("test", new byte[] {1}, 0, 4)]
[InlineData("test", new byte[] {1}, 2, 0)]
public void CanSerializeAndDeserializeTimerInfo(string callback, byte[] state, int dueTime, int period)
{
var timerInfo = new TimerInfo(callback, state, TimeSpan.FromSeconds(dueTime), TimeSpan.FromSeconds(period));
var timerInfo = new TimerInfo(callback, state, TimeSpan.FromSeconds(dueTime), TimeSpan.FromSeconds(period));
// We use strings for ActorId - the result should be the same as passing the Id directly.
var serializedTimerInfo = JsonSerializer.Serialize<TimerInfo>(timerInfo);
// We use strings for ActorId - the result should be the same as passing the Id directly.
var serializedTimerInfo = JsonSerializer.Serialize<TimerInfo>(timerInfo);
var deserializedTimerInfo = JsonSerializer.Deserialize<TimerInfo>(serializedTimerInfo);
var deserializedTimerInfo = JsonSerializer.Deserialize<TimerInfo>(serializedTimerInfo);
Assert.Equal(timerInfo.Callback, deserializedTimerInfo.Callback);
Assert.Equal(timerInfo.Data, deserializedTimerInfo.Data);
Assert.Equal(timerInfo.DueTime, deserializedTimerInfo.DueTime);
Assert.Equal(timerInfo.Period, deserializedTimerInfo.Period);
}
Assert.Equal(timerInfo.Callback, deserializedTimerInfo.Callback);
Assert.Equal(timerInfo.Data, deserializedTimerInfo.Data);
Assert.Equal(timerInfo.DueTime, deserializedTimerInfo.DueTime);
Assert.Equal(timerInfo.Period, deserializedTimerInfo.Period);
}
}
#pragma warning restore 0618

View File

@ -1,159 +1,159 @@
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Communication;
namespace Dapr.Actors
namespace Dapr.Actors;
/// <summary>
/// A Wrapper class for IDaprInteractor which is mainly created for testing.
/// </summary>
public class TestDaprInteractor : IDaprInteractor
{
private TestDaprInteractor _testDaprInteractor;
/// <summary>
/// A Wrapper class for IDaprInteractor which is mainly created for testing.
/// The TestDaprInteractor constructor.
/// </summary>
public class TestDaprInteractor : IDaprInteractor
/// <param name="testDaprInteractor"></param>
public TestDaprInteractor(TestDaprInteractor testDaprInteractor)
{
private TestDaprInteractor _testDaprInteractor;
_testDaprInteractor = testDaprInteractor;
}
/// <summary>
/// The TestDaprInteractor constructor.
/// </summary>
/// <param name="testDaprInteractor"></param>
public TestDaprInteractor(TestDaprInteractor testDaprInteractor)
{
_testDaprInteractor = testDaprInteractor;
}
/// <summary>
/// The TestDaprInteractor constructor.
/// </summary>
public TestDaprInteractor()
{
/// <summary>
/// The TestDaprInteractor constructor.
/// </summary>
public TestDaprInteractor()
{
}
}
/// <summary>
/// Register a reminder.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to register.</param>
/// <param name="data">JSON reminder data as per the Dapr spec.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public virtual async Task RegisterReminderAsync(string actorType, string actorId, string reminderName, string data,
CancellationToken cancellationToken = default)
{
await _testDaprInteractor.RegisterReminderAsync(actorType, actorId, reminderName, data);
}
/// <summary>
/// Register a reminder.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to register.</param>
/// <param name="data">JSON reminder data as per the Dapr spec.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public virtual async Task RegisterReminderAsync(string actorType, string actorId, string reminderName, string data,
CancellationToken cancellationToken = default)
{
await _testDaprInteractor.RegisterReminderAsync(actorType, actorId, reminderName, data);
}
/// <summary>
/// Invokes an Actor method on Dapr without remoting.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="methodName">Method name to invoke.</param>
/// <param name="jsonPayload">Serialized body.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task<Stream> InvokeActorMethodWithoutRemotingAsync(string actorType, string actorId, string methodName,
string jsonPayload, CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Invokes an Actor method on Dapr without remoting.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="methodName">Method name to invoke.</param>
/// <param name="jsonPayload">Serialized body.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task<Stream> InvokeActorMethodWithoutRemotingAsync(string actorType, string actorId, string methodName,
string jsonPayload, CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Saves state batch to Dapr.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="data">JSON data with state changes as per the Dapr spec for transaction state update.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public virtual async Task SaveStateTransactionallyAsync(string actorType, string actorId, string data,
CancellationToken cancellationToken = default)
{
await _testDaprInteractor.SaveStateTransactionallyAsync(actorType, actorId, data);
}
/// <summary>
/// Saves state batch to Dapr.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="data">JSON data with state changes as per the Dapr spec for transaction state update.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public virtual async Task SaveStateTransactionallyAsync(string actorType, string actorId, string data,
CancellationToken cancellationToken = default)
{
await _testDaprInteractor.SaveStateTransactionallyAsync(actorType, actorId, data);
}
/// <summary>
/// Saves a state to Dapr.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="keyName">Name of key to get value for.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public virtual async Task<ActorStateResponse<string>> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
{
return await _testDaprInteractor.GetStateAsync(actorType, actorId, keyName);
}
/// <summary>
/// Saves a state to Dapr.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="keyName">Name of key to get value for.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public virtual async Task<ActorStateResponse<string>> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
{
return await _testDaprInteractor.GetStateAsync(actorType, actorId, keyName);
}
/// <summary>
/// Invokes Actor method.
/// </summary>
/// <param name="serializersManager">Serializers manager for remoting calls.</param>
/// <param name="remotingRequestRequestMessage">Actor Request Message.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
Task<IActorResponseMessage> IDaprInteractor.InvokeActorMethodWithRemotingAsync(ActorMessageSerializersManager serializersManager,
IActorRequestMessage remotingRequestRequestMessage, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Invokes Actor method.
/// </summary>
/// <param name="serializersManager">Serializers manager for remoting calls.</param>
/// <param name="remotingRequestRequestMessage">Actor Request Message.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
Task<IActorResponseMessage> IDaprInteractor.InvokeActorMethodWithRemotingAsync(ActorMessageSerializersManager serializersManager,
IActorRequestMessage remotingRequestRequestMessage, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets a reminder.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to unregister.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task<Stream> GetReminderAsync(string actorType, string actorId, string reminderName,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets a reminder.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to unregister.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public virtual async Task<HttpResponseMessage> GetReminderAsync(string actorType, string actorId, string reminderName,
CancellationToken cancellationToken = default)
{
return await _testDaprInteractor.GetReminderAsync(actorType, actorId, reminderName);
}
/// <summary>
/// Unregisters a reminder.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to unregister.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task UnregisterReminderAsync(string actorType, string actorId, string reminderName,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Unregisters a reminder.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to unregister.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task UnregisterReminderAsync(string actorType, string actorId, string reminderName,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Registers a timer.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="timerName">Name of timer to register.</param>
/// <param name="data">JSON reminder data as per the Dapr spec.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task RegisterTimerAsync(string actorType, string actorId, string timerName, string data,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Registers a timer.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="timerName">Name of timer to register.</param>
/// <param name="data">JSON reminder data as per the Dapr spec.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task RegisterTimerAsync(string actorType, string actorId, string timerName, string data,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Unregisters a timer.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="timerName">Name of timer to register.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task UnregisterTimerAsync(string actorType, string actorId, string timerName,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Unregisters a timer.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorId">ActorId.</param>
/// <param name="timerName">Name of timer to register.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public Task UnregisterTimerAsync(string actorType, string actorId, string timerName,
CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
}

View File

@ -16,7 +16,7 @@ using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Dapr.AspNetCore.IntegrationTest.App;
using FluentAssertions;
using Shouldly;
using Newtonsoft.Json;
using Xunit;
@ -43,7 +43,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var responseUserInfo = JsonConvert.DeserializeObject<UserInfo>(responseContent);
responseUserInfo.Name.Should().Be(userInfo.Name);
responseUserInfo.Name.ShouldBe(userInfo.Name);
}
}
@ -64,7 +64,7 @@ namespace Dapr.AspNetCore.IntegrationTest
request.Headers.Add("Dapr-Api-Token", "asdfgh");
var response = await httpClient.SendAsync(request);
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
}
}
}

View File

@ -19,7 +19,7 @@ namespace Dapr.AspNetCore.IntegrationTest
using System.Text.Json;
using System.Threading.Tasks;
using Dapr.AspNetCore.IntegrationTest.App;
using FluentAssertions;
using Shouldly;
using Xunit;
public class CloudEventsIntegrationTest
@ -48,6 +48,7 @@ namespace Dapr.AspNetCore.IntegrationTest
}
}
[Fact]
public async Task CanSendStructuredCloudEvent()
{
@ -74,7 +75,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var userInfo = await JsonSerializer.DeserializeAsync<UserInfo>(await response.Content.ReadAsStreamAsync(), this.options);
userInfo.Name.Should().Be("jimmy");
userInfo.Name.ShouldBe("jimmy");
}
}
@ -105,7 +106,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var userInfo = await JsonSerializer.DeserializeAsync<UserInfo>(await response.Content.ReadAsStreamAsync(), this.options);
userInfo.Name.Should().Be("jimmy");
userInfo.Name.ShouldBe("jimmy");
}
}
@ -133,7 +134,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var user = await response.Content.ReadAsStringAsync();
user.Should().Be("jimmy \"the cool guy\" smith");
user.ShouldBe("jimmy \"the cool guy\" smith");
}
}
@ -163,7 +164,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var userInfo = await JsonSerializer.DeserializeAsync<UserInfo>(await response.Content.ReadAsStreamAsync(), this.options);
userInfo.Name.Should().Be("jimmy");
userInfo.Name.ShouldBe("jimmy");
}
}
}

View File

@ -16,7 +16,7 @@ namespace Dapr.AspNetCore.IntegrationTest
using System.Net.Http;
using System.Threading.Tasks;
using Dapr.AspNetCore.IntegrationTest.App;
using FluentAssertions;
using Shouldly;
using Newtonsoft.Json;
using Xunit;
@ -37,7 +37,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var widget = await daprClient.GetStateAsync<Widget>("testStore", "test");
widget.Count.Should().Be(18);
widget.Count.ShouldBe(18);
}
}
@ -56,8 +56,8 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var responseWidget = JsonConvert.DeserializeObject<Widget>(responseContent);
responseWidget.Size.Should().Be(widget.Size);
responseWidget.Count.Should().Be(widget.Count);
responseWidget.Size.ShouldBe(widget.Size);
responseWidget.Count.ShouldBe(widget.Count);
}
}
@ -92,7 +92,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var widget = await daprClient.GetStateAsync<Widget>("testStore", "test");
widget.Count.Should().Be(18);
widget.Count.ShouldBe(18);
}
}
@ -111,7 +111,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var widget = await daprClient.GetStateAsync<Widget>("testStore", "test");
widget.Count.Should().Be(18);
widget.Count.ShouldBe(18);
}
}
@ -130,8 +130,8 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var responseWidget = JsonConvert.DeserializeObject<Widget>(responseContent);
responseWidget.Size.Should().Be(widget.Size);
responseWidget.Count.Should().Be(widget.Count);
responseWidget.Size.ShouldBe(widget.Size);
responseWidget.Count.ShouldBe(widget.Count);
}
}

View File

@ -5,7 +5,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Shouldly"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<!-- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />-->
<PackageReference Include="xunit" />

View File

@ -16,7 +16,7 @@ namespace Dapr.AspNetCore.IntegrationTest
using System.Net.Http;
using System.Threading.Tasks;
using Dapr.AspNetCore.IntegrationTest.App;
using FluentAssertions;
using Shouldly;
using Xunit;
public class RoutingIntegrationTest
@ -36,7 +36,7 @@ namespace Dapr.AspNetCore.IntegrationTest
response.EnsureSuccessStatusCode();
var widget = await daprClient.GetStateAsync<Widget>("testStore", "test");
widget.Count.Should().Be(18);
widget.Count.ShouldBe(18);
}
}
}

View File

@ -19,7 +19,7 @@ namespace Dapr.AspNetCore.IntegrationTest
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using FluentAssertions;
using Shouldly;
using Xunit;
public class SubscribeEndpointTest
@ -39,8 +39,8 @@ namespace Dapr.AspNetCore.IntegrationTest
{
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
json.ValueKind.Should().Be(JsonValueKind.Array);
json.GetArrayLength().Should().Be(18);
json.ValueKind.ShouldBe(JsonValueKind.Array);
json.GetArrayLength().ShouldBe(18);
var subscriptions = new List<(string PubsubName, string Topic, string Route, string rawPayload,
string match, string metadata, string DeadLetterTopic, string bulkSubscribeMetadata)>();
@ -109,34 +109,34 @@ namespace Dapr.AspNetCore.IntegrationTest
}
}
subscriptions.Should().Contain(("testpubsub", "A", "topic-a", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("testpubsub", "A.1", "topic-a", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "B", "B", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("custom-pubsub", "custom-C", "C", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "register-user", "register-user", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "register-user-plaintext", "register-user-plaintext", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "D", "D", "true", string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "E", "E", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "E", "E-Critical", string.Empty, "event.type == \"critical\"", string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "E", "E-Important", string.Empty, "event.type == \"important\"", string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "F", "multiTopicAttr", string.Empty, string.Empty, string.Empty, string.Empty,
subscriptions.ShouldContain(("testpubsub", "A", "topic-a", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("testpubsub", "A.1", "topic-a", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "B", "B", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("custom-pubsub", "custom-C", "C", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "register-user", "register-user", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "register-user-plaintext", "register-user-plaintext", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "D", "D", "true", string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "E", "E", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "E", "E-Critical", string.Empty, "event.type == \"critical\"", string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "E", "E-Important", string.Empty, "event.type == \"important\"", string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "F", "multiTopicAttr", string.Empty, string.Empty, string.Empty, string.Empty,
"{\"enabled\":true,\"maxMessagesCount\":100,\"maxAwaitDurationMs\":1000}"));
subscriptions.Should().Contain(("pubsub", "F.1", "multiTopicAttr", "true", string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "G", "G", string.Empty, string.Empty, string.Empty, "deadLetterTopicName",
subscriptions.ShouldContain(("pubsub", "F.1", "multiTopicAttr", "true", string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "G", "G", string.Empty, string.Empty, string.Empty, "deadLetterTopicName",
"{\"enabled\":true,\"maxMessagesCount\":300,\"maxAwaitDurationMs\":1000}"));
subscriptions.Should().Contain(("pubsub", "splitTopicBuilder", "splitTopics", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "splitTopicAttr", "splitTopics", "true", string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "metadata", "multiMetadataTopicAttr", string.Empty, string.Empty, "n1=v1;n2=v2,v3", string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "metadata.1", "multiMetadataTopicAttr", "true", string.Empty, "n1=v1", string.Empty,
subscriptions.ShouldContain(("pubsub", "splitTopicBuilder", "splitTopics", string.Empty, string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "splitTopicAttr", "splitTopics", "true", string.Empty, string.Empty, string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "metadata", "multiMetadataTopicAttr", string.Empty, string.Empty, "n1=v1;n2=v2,v3", string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "metadata.1", "multiMetadataTopicAttr", "true", string.Empty, "n1=v1", string.Empty,
"{\"enabled\":true,\"maxMessagesCount\":500,\"maxAwaitDurationMs\":2000}"));
subscriptions.Should().Contain(("pubsub", "splitMetadataTopicBuilder", "splitMetadataTopics", string.Empty, string.Empty, "n1=v1;n2=v1", string.Empty, String.Empty));
subscriptions.Should().Contain(("pubsub", "metadataseparatorbyemptytring", "topicmetadataseparatorattrbyemptytring", string.Empty, string.Empty, "n1=v1,", string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "splitMetadataTopicBuilder", "splitMetadataTopics", string.Empty, string.Empty, "n1=v1;n2=v1", string.Empty, String.Empty));
subscriptions.ShouldContain(("pubsub", "metadataseparatorbyemptytring", "topicmetadataseparatorattrbyemptytring", string.Empty, string.Empty, "n1=v1,", string.Empty, String.Empty));
// Test priority route sorting
var eTopic = subscriptions.FindAll(e => e.Topic == "E");
eTopic.Count.Should().Be(3);
eTopic[0].Route.Should().Be("E-Critical");
eTopic[1].Route.Should().Be("E-Important");
eTopic[2].Route.Should().Be("E");
eTopic.Count.ShouldBe(3);
eTopic[0].Route.ShouldBe("E-Critical");
eTopic[1].Route.ShouldBe("E-Important");
eTopic[2].Route.ShouldBe("E");
}
}
}

View File

@ -20,7 +20,7 @@ namespace Dapr.AspNetCore.Test
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using FluentAssertions;
using Shouldly;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Xunit;
@ -44,8 +44,8 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(contentType);
ReadBody(httpContext.Request.Body).Should().Be("Hello, world!");
httpContext.Request.ContentType.ShouldBe(contentType);
ReadBody(httpContext.Request.Body).ShouldBe("Hello, world!");
return Task.CompletedTask;
});
@ -77,8 +77,8 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).Should().Be("{\"name\":\"jimmy\"}");
httpContext.Request.ContentType.ShouldBe(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).ShouldBe("{\"name\":\"jimmy\"}");
return Task.CompletedTask;
});
@ -117,8 +117,8 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).Should().Be("{\"name\":\"jimmy\"}");
httpContext.Request.ContentType.ShouldBe(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).ShouldBe("{\"name\":\"jimmy\"}");
return Task.CompletedTask;
});
@ -160,11 +160,13 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).Should().Be("{\"name\":\"jimmy\"}");
httpContext.Request.ContentType.ShouldBe(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).ShouldBe("{\"name\":\"jimmy\"}");
httpContext.Request.Headers.Should().ContainKey("Cloudevent.type").WhichValue.Should().BeEquivalentTo("Test.Type");
httpContext.Request.Headers.Should().ContainKey("Cloudevent.subject").WhichValue.Should().BeEquivalentTo("Test.Subject");
httpContext.Request.Headers.ShouldContainKey("Cloudevent.type");
httpContext.Request.Headers["Cloudevent.type"].ToString().ShouldBe("Test.Type");
httpContext.Request.Headers.ShouldContainKey("Cloudevent.subject");
httpContext.Request.Headers["Cloudevent.subject"].ToString().ShouldBe("Test.Subject");
return Task.CompletedTask;
});
@ -207,11 +209,12 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).Should().Be("{\"name\":\"jimmy\"}");
httpContext.Request.ContentType.ShouldBe(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).ShouldBe("{\"name\":\"jimmy\"}");
httpContext.Request.Headers.Should().ContainKey("Cloudevent.type").WhichValue.Should().BeEquivalentTo("Test.Type");
httpContext.Request.Headers.Should().NotContainKey("Cloudevent.subject");
httpContext.Request.Headers.ShouldContainKey("Cloudevent.type");
httpContext.Request.Headers["Cloudevent.type"].ToString().ShouldBe("Test.Type");
httpContext.Request.Headers.ShouldNotContainKey("Cloudevent.subject");
return Task.CompletedTask;
});
@ -254,11 +257,12 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).Should().Be("{\"name\":\"jimmy\"}");
httpContext.Request.ContentType.ShouldBe(dataContentType ?? "application/json");
ReadBody(httpContext.Request.Body).ShouldBe("{\"name\":\"jimmy\"}");
httpContext.Request.Headers.Should().NotContainKey("Cloudevent.type");
httpContext.Request.Headers.Should().ContainKey("Cloudevent.subject").WhichValue.Should().BeEquivalentTo("Test.Subject");
httpContext.Request.Headers.ShouldNotContainKey("Cloudevent.type");
httpContext.Request.Headers.ShouldContainKey("Cloudevent.subject");
httpContext.Request.Headers["Cloudevent.subject"].ToString().ShouldBe("Test.Subject");
return Task.CompletedTask;
});
@ -297,8 +301,8 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be("text/plain");
ReadBody(httpContext.Request.Body).Should().Be(expected);
httpContext.Request.ContentType.ShouldBe("text/plain");
ReadBody(httpContext.Request.Body).ShouldBe(expected);
return Task.CompletedTask;
});
@ -331,8 +335,8 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be("text/plain");
ReadBody(httpContext.Request.Body).Should().Be(expected);
httpContext.Request.ContentType.ShouldBe("text/plain");
ReadBody(httpContext.Request.Body).ShouldBe(expected);
return Task.CompletedTask;
});
@ -366,8 +370,8 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be("application/person+json");
ReadBody(httpContext.Request.Body).Should().Be("{\"name\":\"jimmy\"}");
httpContext.Request.ContentType.ShouldBe("application/person+json");
ReadBody(httpContext.Request.Body).ShouldBe("{\"name\":\"jimmy\"}");
return Task.CompletedTask;
});
@ -396,14 +400,14 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be(dataContentType);
httpContext.Request.ContentType.ShouldBe(dataContentType);
var bytes = new byte[httpContext.Request.Body.Length];
#if NET9_0
httpContext.Request.Body.ReadExactly(bytes, 0, bytes.Length);
#else
httpContext.Request.Body.Read(bytes, 0, bytes.Length);
#endif
bytes.Should().Equal(data);
bytes.ShouldBe(data);
return Task.CompletedTask;
});
@ -432,9 +436,9 @@ namespace Dapr.AspNetCore.Test
// Do verification in the scope of the middleware
app.Run(httpContext =>
{
httpContext.Request.ContentType.Should().Be("application/json");
httpContext.Request.ContentType.ShouldBe("application/json");
var body = ReadBody(httpContext.Request.Body);
body.Should().Equals(data);
body.ShouldBe(data);
return Task.CompletedTask;
});
@ -447,7 +451,7 @@ namespace Dapr.AspNetCore.Test
MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data_base64\": \"{base64Str}\", \"data\": {data} }}");
await pipeline.Invoke(context);
context.Response.StatusCode.Should().Be((int)HttpStatusCode.BadRequest);
context.Response.StatusCode.ShouldBe((int)HttpStatusCode.BadRequest);
}
private static Stream MakeBody(string text, Encoding encoding = null)

View File

@ -5,8 +5,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Shouldly"/>
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>

View File

@ -18,7 +18,7 @@ namespace Dapr.AspNetCore.Test
using System.Linq;
using System.Reflection;
using Dapr.AspNetCore.Resources;
using FluentAssertions;
using Shouldly;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -34,9 +34,7 @@ namespace Dapr.AspNetCore.Test
Action action = () => provider.OnProvidersExecuted(context);
action
.Should()
.NotThrow<NullReferenceException>();
action.ShouldNotThrow();
}
[Fact]
@ -48,8 +46,7 @@ namespace Dapr.AspNetCore.Test
Action action = () => provider.OnProvidersExecuted(context);
action
.Should()
.Throw<InvalidOperationException>(SR.ErrorStateStoreNameNotProvidedForStateEntry);
.ShouldThrow<InvalidOperationException>(SR.ErrorStateStoreNameNotProvidedForStateEntry);
}
private ApplicationModelProviderContext CreateContext(string methodName)

View File

@ -18,7 +18,7 @@ namespace Dapr.AspNetCore.Test
using System.Threading.Tasks;
using Dapr.Client;
using Dapr.Client.Autogen.Grpc.v1;
using FluentAssertions;
using Shouldly;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -38,9 +38,9 @@ namespace Dapr.AspNetCore.Test
await binder.BindModelAsync(context);
context.Result.IsModelSet.Should().BeFalse();
context.ModelState.ErrorCount.Should().Be(1);
context.ModelState["testParameter"].Errors.Count.Should().Be(1);
context.Result.IsModelSet.ShouldBeFalse();
context.ModelState.ErrorCount.ShouldBe(1);
context.ModelState["testParameter"].Errors.Count.ShouldBe(1);
// No request to state store, validated by disposing client
}
@ -66,12 +66,12 @@ namespace Dapr.AspNetCore.Test
await SendResponseWithState(state, request);
// Get response and validate
context.Result.IsModelSet.Should().BeTrue();
context.Result.Model.As<Widget>().Size.Should().Be("small");
context.Result.Model.As<Widget>().Color.Should().Be("yellow");
context.Result.IsModelSet.ShouldBeTrue();
((Widget)context.Result.Model).Size.ShouldBe("small");
((Widget)context.Result.Model).Color.ShouldBe("yellow");
context.ValidationState.Count.Should().Be(1);
context.ValidationState[context.Result.Model].SuppressValidation.Should().BeTrue();
context.ValidationState.Count.ShouldBe(1);
context.ValidationState[context.Result.Model].SuppressValidation.ShouldBeTrue();
}
[Fact]
@ -95,13 +95,13 @@ namespace Dapr.AspNetCore.Test
await SendResponseWithState(state, request);
// Get response and validate
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");
context.Result.Model.As<StateEntry<Widget>>().Value.Color.Should().Be("yellow");
context.Result.IsModelSet.ShouldBeTrue();
((StateEntry<Widget>)context.Result.Model).Key.ShouldBe("test");
((StateEntry<Widget>)context.Result.Model).Value.Size.ShouldBe("small");
((StateEntry<Widget>)context.Result.Model).Value.Color.ShouldBe("yellow");
context.ValidationState.Count.Should().Be(1);
context.ValidationState[context.Result.Model].SuppressValidation.Should().BeTrue();
context.ValidationState.Count.ShouldBe(1);
context.ValidationState[context.Result.Model].SuppressValidation.ShouldBeTrue();
}
[Fact]
@ -122,9 +122,9 @@ namespace Dapr.AspNetCore.Test
await SendResponseWithState<string>(null, request);
context.ModelState.IsValid.Should().BeTrue();
context.Result.IsModelSet.Should().BeFalse();
context.Result.Should().Be(ModelBindingResult.Failed());
context.ModelState.IsValid.ShouldBeTrue();
context.Result.IsModelSet.ShouldBeFalse();
context.Result.ShouldBe(ModelBindingResult.Failed());
}
[Fact]
@ -145,9 +145,9 @@ namespace Dapr.AspNetCore.Test
await SendResponseWithState<string>(null, request);
context.ModelState.IsValid.Should().BeTrue();
context.Result.IsModelSet.Should().BeTrue();
((StateEntry<Widget>)context.Result.Model).Value.Should().BeNull();
context.ModelState.IsValid.ShouldBeTrue();
context.Result.IsModelSet.ShouldBeTrue();
((StateEntry<Widget>)context.Result.Model).Value.ShouldBeNull();
}
private static ModelBindingContext CreateContext(IServiceProvider services)

View File

@ -19,7 +19,7 @@ namespace Dapr.Client.Test
using System.Threading;
using System.Threading.Tasks;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
using FluentAssertions;
using Shouldly;
using Grpc.Core;
using Moq;
using Xunit;
@ -43,26 +43,24 @@ namespace Dapr.Client.Test
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.BulkPublishRequest>();
envelope.Entries.Count.Should().Be(2);
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be(TestTopicName);
envelope.Metadata.Count.Should().Be(0);
envelope.Entries.Count.ShouldBe(2);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe(TestTopicName);
envelope.Metadata.Count.ShouldBe(0);
var firstEntry = envelope.Entries[0];
firstEntry.EntryId.Should().Be("0");
firstEntry.ContentType.Should().Be(TestContentType);
firstEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.Should().BeEmpty();
firstEntry.EntryId.ShouldBe("0");
firstEntry.ContentType.ShouldBe(TestContentType);
firstEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.ShouldBeEmpty();
var secondEntry = envelope.Entries[1];
secondEntry.EntryId.Should().Be("1");
secondEntry.ContentType.Should().Be(TestContentType);
secondEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.Should().BeEmpty();
secondEntry.EntryId.ShouldBe("1");
secondEntry.ContentType.ShouldBe(TestContentType);
secondEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.ShouldBeEmpty();
// Create Response & Respond
var response = new Autogenerated.BulkPublishResponse
@ -72,7 +70,7 @@ namespace Dapr.Client.Test
var bulkPublishResponse = await request.CompleteWithMessageAsync(response);
// Get response and validate
bulkPublishResponse.FailedEntries.Count.Should().Be(0);
bulkPublishResponse.FailedEntries.Count.ShouldBe(0);
}
[Fact]
@ -90,31 +88,29 @@ namespace Dapr.Client.Test
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.BulkPublishRequest>();
envelope.Entries.Count.Should().Be(2);
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be(TestTopicName);
envelope.Entries.Count.ShouldBe(2);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe(TestTopicName);
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");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
var firstEntry = envelope.Entries[0];
firstEntry.EntryId.Should().Be("0");
firstEntry.ContentType.Should().Be(TestContentType);
firstEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.Should().BeEmpty();
firstEntry.EntryId.ShouldBe("0");
firstEntry.ContentType.ShouldBe(TestContentType);
firstEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.ShouldBeEmpty();
var secondEntry = envelope.Entries[1];
secondEntry.EntryId.Should().Be("1");
secondEntry.ContentType.Should().Be(TestContentType);
secondEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.Should().BeEmpty();
secondEntry.EntryId.ShouldBe("1");
secondEntry.ContentType.ShouldBe(TestContentType);
secondEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.ShouldBeEmpty();
// Create Response & Respond
var response = new Autogenerated.BulkPublishResponse
@ -124,7 +120,7 @@ namespace Dapr.Client.Test
var bulkPublishResponse = await request.CompleteWithMessageAsync(response);
// Get response and validate
bulkPublishResponse.FailedEntries.Count.Should().Be(0);
bulkPublishResponse.FailedEntries.Count.ShouldBe(0);
}
[Fact]
@ -139,26 +135,24 @@ namespace Dapr.Client.Test
request.Dismiss();
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.BulkPublishRequest>();
envelope.Entries.Count.Should().Be(2);
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be(TestTopicName);
envelope.Metadata.Count.Should().Be(0);
envelope.Entries.Count.ShouldBe(2);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe(TestTopicName);
envelope.Metadata.Count.ShouldBe(0);
var firstEntry = envelope.Entries[0];
firstEntry.EntryId.Should().Be("0");
firstEntry.ContentType.Should().Be(TestContentType);
firstEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.Should().BeEmpty();
firstEntry.EntryId.ShouldBe("0");
firstEntry.ContentType.ShouldBe(TestContentType);
firstEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.ShouldBeEmpty();
var secondEntry = envelope.Entries[1];
secondEntry.EntryId.Should().Be("1");
secondEntry.ContentType.Should().Be(TestContentType);
secondEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.Should().BeEmpty();
secondEntry.EntryId.ShouldBe("1");
secondEntry.ContentType.ShouldBe(TestContentType);
secondEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.ShouldBeEmpty();
// Create Response & Respond
var response = new Autogenerated.BulkPublishResponse
@ -168,7 +162,7 @@ namespace Dapr.Client.Test
var bulkPublishResponse = await request.CompleteWithMessageAsync(response);
// Get response and validate
bulkPublishResponse.FailedEntries.Count.Should().Be(0);
bulkPublishResponse.FailedEntries.Count.ShouldBe(0);
}
[Fact]
@ -185,31 +179,29 @@ namespace Dapr.Client.Test
request.Dismiss();
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.BulkPublishRequest>();
envelope.Entries.Count.Should().Be(2);
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be(TestTopicName);
envelope.Entries.Count.ShouldBe(2);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe(TestTopicName);
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");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
var firstEntry = envelope.Entries[0];
firstEntry.EntryId.Should().Be("0");
firstEntry.ContentType.Should().Be(TestContentType);
firstEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.Should().BeEmpty();
firstEntry.EntryId.ShouldBe("0");
firstEntry.ContentType.ShouldBe(TestContentType);
firstEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[0], client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.ShouldBeEmpty();
var secondEntry = envelope.Entries[1];
secondEntry.EntryId.Should().Be("1");
secondEntry.ContentType.Should().Be(TestContentType);
secondEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.Should().BeEmpty();
secondEntry.EntryId.ShouldBe("1");
secondEntry.ContentType.ShouldBe(TestContentType);
secondEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishData[1], client.InnerClient.JsonSerializerOptions));
secondEntry.Metadata.ShouldBeEmpty();
// Create Response & Respond
var response = new Autogenerated.BulkPublishResponse
@ -219,7 +211,7 @@ namespace Dapr.Client.Test
var bulkPublishResponse = await request.CompleteWithMessageAsync(response);
// Get response and validate
bulkPublishResponse.FailedEntries.Count.Should().Be(0);
bulkPublishResponse.FailedEntries.Count.ShouldBe(0);
}
[Fact]
@ -236,18 +228,17 @@ namespace Dapr.Client.Test
request.Dismiss();
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.BulkPublishRequest>();
envelope.Entries.Count.Should().Be(1);
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be(TestTopicName);
envelope.Metadata.Count.Should().Be(0);
envelope.Entries.Count.ShouldBe(1);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe(TestTopicName);
envelope.Metadata.Count.ShouldBe(0);
var firstEntry = envelope.Entries[0];
firstEntry.EntryId.Should().Be("0");
firstEntry.ContentType.Should().Be(TestContentType);
firstEntry.Event.ToStringUtf8().Should()
.Be(JsonSerializer.Serialize(bulkPublishDataObject, client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.Should().BeEmpty();
firstEntry.EntryId.ShouldBe("0");
firstEntry.ContentType.ShouldBe(TestContentType);
firstEntry.Event.ToStringUtf8().ShouldBe(JsonSerializer.Serialize(bulkPublishDataObject, client.InnerClient.JsonSerializerOptions));
firstEntry.Metadata.ShouldBeEmpty();
// Create Response & Respond
var response = new Autogenerated.BulkPublishResponse
@ -257,7 +248,7 @@ namespace Dapr.Client.Test
var bulkPublishResponse = await request.CompleteWithMessageAsync(response);
// Get response and validate
bulkPublishResponse.FailedEntries.Count.Should().Be(0);
bulkPublishResponse.FailedEntries.Count.ShouldBe(0);
}
[Fact]
@ -333,11 +324,11 @@ namespace Dapr.Client.Test
var bulkPublishResponse = await request.CompleteWithMessageAsync(response);
// Get response and validate
bulkPublishResponse.FailedEntries[0].Entry.EntryId.Should().Be("0");
bulkPublishResponse.FailedEntries[0].ErrorMessage.Should().Be("Failed to publish");
bulkPublishResponse.FailedEntries[0].Entry.EntryId.ShouldBe("0");
bulkPublishResponse.FailedEntries[0].ErrorMessage.ShouldBe("Failed to publish");
bulkPublishResponse.FailedEntries[1].Entry.EntryId.Should().Be("1");
bulkPublishResponse.FailedEntries[1].ErrorMessage.Should().Be("Failed to publish");
bulkPublishResponse.FailedEntries[1].Entry.EntryId.ShouldBe("1");
bulkPublishResponse.FailedEntries[1].ErrorMessage.ShouldBe("Failed to publish");
}
private class Widget

View File

@ -15,7 +15,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
using Xunit;
using FluentAssertions;
using Shouldly;
namespace Dapr.Client.Test
{
@ -37,13 +37,13 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetConfigurationRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Keys.Should().Contain("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");
envelope.StoreName.ShouldBe("testStore");
envelope.Keys.ShouldContain("test_key");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
// Get response and validate
var invokeResponse = new Autogenerated.GetConfigurationResponse();
@ -55,8 +55,8 @@ namespace Dapr.Client.Test
var domainResponse = await request.CompleteWithMessageAsync(invokeResponse);
var configItem = domainResponse.Items["testKey"];
configItem.Value.Should().Be("testValue");
configItem.Version.Should().Be("v1");
configItem.Value.ShouldBe("testValue");
configItem.Version.ShouldBe("v1");
}
[Fact]
@ -75,13 +75,13 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetConfigurationRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Keys.Should().BeEmpty();
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Keys.ShouldBeEmpty();
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
// Get response and validate
var invokeResponse = new Autogenerated.GetConfigurationResponse();
@ -93,8 +93,8 @@ namespace Dapr.Client.Test
var domainResponse = await request.CompleteWithMessageAsync(invokeResponse);
var configItem = domainResponse.Items["testKey"];
configItem.Value.Should().Be("testValue");
configItem.Version.Should().Be("v1");
configItem.Value.ShouldBe("testValue");
configItem.Version.ShouldBe("v1");
}
[Fact]
@ -108,9 +108,9 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetConfigurationRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Keys.Should().Contain("test_key");
envelope.Metadata.Count.Should().Be(0);
envelope.StoreName.ShouldBe("testStore");
envelope.Keys.ShouldContain("test_key");
envelope.Metadata.Count.ShouldBe(0);
// Get response and validate
var invokeResponse = new Autogenerated.GetConfigurationResponse();
@ -122,8 +122,8 @@ namespace Dapr.Client.Test
var domainResponse = await request.CompleteWithMessageAsync(invokeResponse);
var configItem = domainResponse.Items["testKey"];
configItem.Value.Should().Be("testValue");
configItem.Version.Should().Be("v1");
configItem.Value.ShouldBe("testValue");
configItem.Version.ShouldBe("v1");
}
[Fact]
@ -136,8 +136,8 @@ namespace Dapr.Client.Test
});
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.UnsubscribeConfigurationRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Id.Should().Be("testId");
envelope.StoreName.ShouldBe("testStore");
envelope.Id.ShouldBe("testId");
request.Dismiss();
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
@ -30,28 +29,6 @@ namespace Dapr.Client.Test
(ReadOnlyMemory<byte>) Array.Empty<byte>(), keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), CancellationToken.None));
}
[Fact]
public async Task EncryptAsync_Stream_VaultResourceName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string vaultResourceName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.EncryptAsync(vaultResourceName,
new MemoryStream(), "MyKey", new EncryptionOptions(KeyWrapAlgorithm.Rsa),
CancellationToken.None));
}
[Fact]
public async Task EncryptAsync_Stream_KeyName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string keyName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.EncryptAsync("myVault",
(Stream) new MemoryStream(), keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa),
CancellationToken.None));
}
[Fact]
public async Task DecryptAsync_ByteArray_VaultResourceName_ArgumentVerifierException()
{
@ -71,25 +48,5 @@ namespace Dapr.Client.Test
await Assert.ThrowsAsync<ArgumentException>(async () => await client.DecryptAsync("myVault",
Array.Empty<byte>(), keyName, new DecryptionOptions(), CancellationToken.None));
}
[Fact]
public async Task DecryptAsync_Stream_VaultResourceName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string vaultResourceName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.DecryptAsync(vaultResourceName,
new MemoryStream(), "MyKey", new DecryptionOptions(), CancellationToken.None));
}
[Fact]
public async Task DecryptAsync_Stream_KeyName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string keyName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.DecryptAsync("myVault",
new MemoryStream(), keyName, new DecryptionOptions(), CancellationToken.None));
}
}
}

View File

@ -5,7 +5,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Core.Testing" />
<PackageReference Include="Grpc.Net.Client" />
@ -14,6 +13,7 @@
<PackageReference Include="moq" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Shouldly"/>
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>

View File

@ -15,7 +15,7 @@ namespace Dapr.Client.Test
{
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Shouldly;
using Xunit;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -38,8 +38,8 @@ namespace Dapr.Client.Test
await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
headerValues.Count().Should().Be(1);
headerValues.First().Should().Be("test_token");
headerValues.Count().ShouldBe(1);
headerValues.First().ShouldBe("test_token");
}
[Fact]
@ -59,7 +59,7 @@ namespace Dapr.Client.Test
await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
headerValues.Should().BeNull();
headerValues.ShouldBeNull();
}
}
}

View File

@ -16,7 +16,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client.Autogen.Grpc.v1;
using Dapr.Client.Autogen.Test.Grpc.v1;
using FluentAssertions;
using Shouldly;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Grpc.Net.Client;
@ -62,9 +62,9 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc);
envelope.Id.ShouldBe("test");
envelope.Message.Method.ShouldBe("test");
envelope.Message.ContentType.ShouldBe(Constants.ContentTypeApplicationGrpc);
// Create Response & Respond
var data = new Response() { Name = "Look, I was invoked!" };
@ -75,7 +75,7 @@ namespace Dapr.Client.Test
// Validate Response
var invokedResponse = await request.CompleteWithMessageAsync(response);
invokedResponse.Name.Should().Be("Look, I was invoked!");
invokedResponse.Name.ShouldBe("Look, I was invoked!");
}
[Fact]
@ -126,9 +126,9 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(string.Empty);
envelope.Id.ShouldBe("test");
envelope.Message.Method.ShouldBe("test");
envelope.Message.ContentType.ShouldBe(string.Empty);
// Create Response & Respond
var data = new Response() { Name = "Look, I was invoked!" };
@ -139,7 +139,7 @@ namespace Dapr.Client.Test
// Validate Response
var invokedResponse = await request.CompleteWithMessageAsync(response);
invokedResponse.Name.Should().Be("Look, I was invoked!");
invokedResponse.Name.ShouldBe("Look, I was invoked!");
}
[Fact]
@ -176,7 +176,7 @@ namespace Dapr.Client.Test
}
[Fact]
public void InvokeMethodGrpcAsync_CanInvokeMethodWithNoReturnTypeAndData()
public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithNoReturnTypeAndData()
{
var request = new Request() { RequestParameter = "Hello " };
var client = new MockClient();
@ -195,7 +195,7 @@ namespace Dapr.Client.Test
.Setup(m => m.InvokeServiceAsync(It.IsAny<Autogen.Grpc.v1.InvokeServiceRequest>(), It.IsAny<CallOptions>()))
.Returns(response);
FluentActions.Awaiting(async () => await client.DaprClient.InvokeMethodGrpcAsync<Request>("test", "test", request)).Should().NotThrowAsync();
await Should.NotThrowAsync(async () => await client.DaprClient.InvokeMethodGrpcAsync<Request>("test", "test", request));
}
[Fact]
@ -250,9 +250,9 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc);
envelope.Id.ShouldBe("test");
envelope.Message.Method.ShouldBe("test");
envelope.Message.ContentType.ShouldBe(Constants.ContentTypeApplicationGrpc);
var actual = envelope.Message.Data.Unpack<Request>();
Assert.Equal(invokeRequest.RequestParameter, actual.RequestParameter);
@ -275,9 +275,9 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc);
envelope.Id.ShouldBe("test");
envelope.Message.Method.ShouldBe("test");
envelope.Message.ContentType.ShouldBe(Constants.ContentTypeApplicationGrpc);
var actual = envelope.Message.Data.Unpack<Request>();
Assert.Equal(invokeRequest.RequestParameter, actual.RequestParameter);
@ -291,7 +291,7 @@ namespace Dapr.Client.Test
// Validate Response
var invokedResponse = await request.CompleteWithMessageAsync(response);
invokedResponse.Name.Should().Be(invokeResponse.Name);
invokedResponse.Name.ShouldBe(invokeResponse.Name);
}
[Fact]
@ -308,7 +308,7 @@ namespace Dapr.Client.Test
var response = await daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "SayHello", request);
response.Name.Should().Be("Hello Look, I was invoked!");
response.Name.ShouldBe("Hello Look, I was invoked!");
}
[Fact]
@ -328,10 +328,10 @@ namespace Dapr.Client.Test
var response = await daprClient.InvokeMethodGrpcAsync<TestRun, TestRun>("test", "TestRun", testRun);
response.Tests.Count.Should().Be(3);
response.Tests[0].Name.Should().Be("test1");
response.Tests[1].Name.Should().Be("test2");
response.Tests[2].Name.Should().Be("test3");
response.Tests.Count.ShouldBe(3);
response.Tests[0].Name.ShouldBe("test1");
response.Tests[1].Name.ShouldBe("test2");
response.Tests[2].Name.ShouldBe("test3");
}
[Fact]
@ -348,7 +348,7 @@ namespace Dapr.Client.Test
var response = await daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "not-existing", request);
response.Name.Should().Be("unexpected");
response.Name.ShouldBe("unexpected");
}
@ -401,10 +401,10 @@ namespace Dapr.Client.Test
// Validate Response
var metadata = await request.CompleteWithMessageAsync(response);
metadata.Id.Should().Be("testId");
metadata.Extended.Should().Contain(new System.Collections.Generic.KeyValuePair<string, string>("e1", "v1"));
metadata.Actors.Should().Contain(actors => actors.Count == 1 && actors.Type == "testType");
metadata.Components.Should().Contain(components => components.Name == "testName" && components.Type == "testType" && components.Version == "V1" && components.Capabilities.Length == 0);
metadata.Id.ShouldBe("testId");
metadata.Extended.ShouldContain(new System.Collections.Generic.KeyValuePair<string, string>("e1", "v1"));
metadata.Actors.ShouldContain(actors => actors.Count == 1 && actors.Type == "testType");
metadata.Components.ShouldContain(components => components.Name == "testName" && components.Type == "testType" && components.Version == "V1" && components.Capabilities.Length == 0);
}
[Fact]
@ -445,8 +445,8 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<SetMetadataRequest>();
envelope.Key.Should().Be("test");
envelope.Value.Should().Be("testv");
envelope.Key.ShouldBe("test");
envelope.Value.ShouldBe("testv");
await request.CompleteWithMessageAsync(new Empty());

View File

@ -14,7 +14,7 @@
using System.Threading.Tasks;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
using Xunit;
using FluentAssertions;
using Shouldly;
using System;
namespace Dapr.Client.Test
@ -37,10 +37,10 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.TryLockRequest>();
envelope.StoreName.Should().Be("redis");
envelope.ResourceId.Should().Be("resourceId");
envelope.LockOwner.Should().Be("owner1");
envelope.ExpiryInSeconds.Should().Be(1000);
envelope.StoreName.ShouldBe("redis");
envelope.ResourceId.ShouldBe("resourceId");
envelope.LockOwner.ShouldBe("owner1");
envelope.ExpiryInSeconds.ShouldBe(1000);
// Get response and validate
var invokeResponse = new Autogenerated.TryLockResponse{
@ -48,7 +48,7 @@ namespace Dapr.Client.Test
};
var domainResponse = await request.CompleteWithMessageAsync(invokeResponse);
domainResponse.Success.Should().Be(true);
domainResponse.Success.ShouldBe(true);
}
[Fact]
@ -80,9 +80,9 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.UnlockRequest>();
envelope.StoreName.Should().Be("redis");
envelope.ResourceId.Should().Be("resourceId");
envelope.LockOwner.Should().Be("owner1");
envelope.StoreName.ShouldBe("redis");
envelope.ResourceId.ShouldBe("resourceId");
envelope.LockOwner.ShouldBe("owner1");
// Get response and validate
var invokeResponse = new Autogenerated.UnlockResponse{
@ -90,7 +90,7 @@ namespace Dapr.Client.Test
};
var domainResponse = await request.CompleteWithMessageAsync(invokeResponse);
domainResponse.status.Should().Be(LockStatus.LockDoesNotExist);
domainResponse.status.ShouldBe(LockStatus.LockDoesNotExist);
}
}
}
}

View File

@ -20,7 +20,7 @@ namespace Dapr.Client.Test
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client.Autogen.Grpc.v1;
using FluentAssertions;
using Shouldly;
using Google.Protobuf;
using Grpc.Core;
using Moq;
@ -43,11 +43,11 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
envelope.Name.Should().Be("test");
envelope.Metadata.Count.Should().Be(0);
envelope.Name.ShouldBe("test");
envelope.Metadata.Count.ShouldBe(0);
var json = envelope.Data.ToStringUtf8();
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello ");
typeFromRequest.RequestParameter.ShouldBe("Hello ");
}
[Fact]
@ -70,15 +70,15 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.Name.ShouldBe("test");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
var json = envelope.Data.ToStringUtf8();
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello ");
typeFromRequest.RequestParameter.ShouldBe("Hello ");
}
[Fact]
@ -95,8 +95,8 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
envelope.Name.Should().Be("test");
envelope.Metadata.Count.Should().Be(0);
envelope.Name.ShouldBe("test");
envelope.Metadata.Count.ShouldBe(0);
var json = envelope.Data.ToStringUtf8();
Assert.Equal("null", json);
}
@ -133,16 +133,16 @@ namespace Dapr.Client.Test
var response = await request.CompleteWithMessageAsync(gRpcResponse);
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");
envelope.Name.ShouldBe("test");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
var json = envelope.Data.ToStringUtf8();
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello ");
typeFromRequest.RequestParameter.ShouldBe("Hello ");
Assert.Same(bindingRequest, response.Request);
Assert.Equal("red", JsonSerializer.Deserialize<Widget>(response.Data.Span, client.InnerClient.JsonSerializerOptions).Color);
@ -239,15 +239,15 @@ namespace Dapr.Client.Test
var response = await req.CompleteWithMessageAsync(resp);
var envelope = await req.GetRequestEnvelopeAsync<InvokeBindingRequest>();
envelope.Name.Should().Be("binding");
envelope.Operation.Should().Be("operation");
envelope.Metadata.Count.Should().Be(1);
envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
envelope.Metadata["key1"].Should().Be("value1");
envelope.Name.ShouldBe("binding");
envelope.Operation.ShouldBe("operation");
envelope.Metadata.Count.ShouldBe(1);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
var json = envelope.Data.ToStringUtf8();
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Test");
typeFromRequest.RequestParameter.ShouldBe("Test");
Assert.Equal("red", response.Color);
}
@ -269,8 +269,8 @@ namespace Dapr.Client.Test
var response = await req.CompleteWithMessageAsync(resp);
var envelope = await req.GetRequestEnvelopeAsync<InvokeBindingRequest>();
envelope.Name.Should().Be("binding");
envelope.Operation.Should().Be("operation");
envelope.Name.ShouldBe("binding");
envelope.Operation.ShouldBe("operation");
Assert.Equal("red", response.Color);
}

View File

@ -24,7 +24,7 @@ namespace Dapr.Client.Test
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client.Autogen.Grpc.v1;
using FluentAssertions;
using Shouldly;
using Grpc.Core;
using Moq;
using Xunit;
@ -49,11 +49,11 @@ namespace Dapr.Client.Test
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);
envelope.DataContentType.ShouldBe("application/json");
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
jsonFromRequest.ShouldBe(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.ShouldBe(0);
}
[Fact]
@ -75,9 +75,8 @@ namespace Dapr.Client.Test
var client = new TestClient<DaprClient>(clientBuilder.Build(), handler);
//Ensure that the JsonStringEnumConverter is registered
client.InnerClient.JsonSerializerOptions.Converters.Count.Should().Be(1);
client.InnerClient.JsonSerializerOptions.Converters.First().GetType().Name.Should()
.Match(nameof(JsonStringEnumConverter));
client.InnerClient.JsonSerializerOptions.Converters.Count.ShouldBe(1);
client.InnerClient.JsonSerializerOptions.Converters.First().GetType().Name.ShouldMatch(nameof(JsonStringEnumConverter));
var publishData = new Widget {Size = "Large", Color = WidgetColor.Red};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
@ -89,9 +88,8 @@ namespace Dapr.Client.Test
var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
var jsonFromRequest = envelope.Data.ToStringUtf8();
jsonFromRequest.Should()
.Be(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions));
jsonFromRequest.Should().Match("{\"Size\":\"Large\",\"Color\":\"Red\"}");
jsonFromRequest.ShouldBe(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions));
jsonFromRequest.ShouldMatch("{\"Size\":\"Large\",\"Color\":\"Red\"}");
}
[Fact]
@ -116,16 +114,16 @@ namespace Dapr.Client.Test
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.DataContentType.ShouldBe("application/json");
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
jsonFromRequest.ShouldBe(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");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
}
[Fact]
@ -142,10 +140,10 @@ namespace Dapr.Client.Test
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(0);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
envelope.Data.Length.ShouldBe(0);
envelope.Metadata.Count.ShouldBe(0);
}
[Fact]
@ -167,15 +165,15 @@ namespace Dapr.Client.Test
request.Dismiss();
var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be("test");
envelope.Data.Length.Should().Be(0);
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
envelope.Data.Length.ShouldBe(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");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
}
[Fact]
@ -200,11 +198,11 @@ namespace Dapr.Client.Test
var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
var jsonFromRequest = envelope.Data.ToStringUtf8();
envelope.DataContentType.Should().Be("application/cloudevents+json");
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be("test");
jsonFromRequest.Should().Be(JsonSerializer.Serialize(cloudEvent, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.Should().Be(0);
envelope.DataContentType.ShouldBe("application/cloudevents+json");
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
jsonFromRequest.ShouldBe(JsonSerializer.Serialize(cloudEvent, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.ShouldBe(0);
}
[Fact]
@ -228,11 +226,11 @@ namespace Dapr.Client.Test
var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
var jsonFromRequest = envelope.Data.ToStringUtf8();
envelope.DataContentType.Should().Be("application/cloudevents+json");
envelope.PubsubName.Should().Be(TestPubsubName);
envelope.Topic.Should().Be("test");
jsonFromRequest.Should().Be(JsonSerializer.Serialize(cloudEvent, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.Should().Be(0);
envelope.DataContentType.ShouldBe("application/cloudevents+json");
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
jsonFromRequest.ShouldBe(JsonSerializer.Serialize(cloudEvent, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.ShouldBe(0);
}
[Fact]
@ -287,13 +285,13 @@ namespace Dapr.Client.Test
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));
envelope.DataContentType.ShouldBe("application/json");
envelope.PubsubName.ShouldBe(TestPubsubName);
envelope.Topic.ShouldBe("test");
jsonFromRequest.ShouldBe(JsonSerializer.Serialize(publishData));
// The default serializer forces camel case, so this should be different from our serialization above.
jsonFromRequest.Should().NotBe(JsonSerializer.Serialize(publishBytes, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.Should().Be(0);
jsonFromRequest.ShouldNotBe(JsonSerializer.Serialize(publishBytes, client.InnerClient.JsonSerializerOptions));
envelope.Metadata.Count.ShouldBe(0);
}
private class PublishData

View File

@ -17,7 +17,7 @@ namespace Dapr.Client.Test
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Shouldly;
using Grpc.Core;
using Moq;
using Xunit;
@ -44,13 +44,13 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test_key");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
}
[Fact]
@ -72,13 +72,13 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test_key");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
// Create Response & Respond
var secrets = new Dictionary<string, string>
@ -88,9 +88,9 @@ namespace Dapr.Client.Test
var secretsResponse = await SendResponseWithSecrets(secrets, request);
// Get response and validate
secretsResponse.Count.Should().Be(1);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"].Should().Be("Guess_Redis");
secretsResponse.Count.ShouldBe(1);
secretsResponse.ContainsKey("redis_secret").ShouldBeTrue();
secretsResponse["redis_secret"].ShouldBe("Guess_Redis");
}
[Fact]
@ -107,16 +107,16 @@ namespace Dapr.Client.Test
//Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("us-west-1/org/xpto/secretabc");
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("us-west-1/org/xpto/secretabc");
var secrets = new Dictionary<string, string> { { "us-west-1/org/xpto/secretabc", "abc123" } };
var secretsResponse = await SendResponseWithSecrets(secrets, request);
//Get response and validate
secretsResponse.Count.Should().Be(1);
secretsResponse.ContainsKey("us-west-1/org/xpto/secretabc").Should().BeTrue();
secretsResponse["us-west-1/org/xpto/secretabc"].Should().Be("abc123");
secretsResponse.Count.ShouldBe(1);
secretsResponse.ContainsKey("us-west-1/org/xpto/secretabc").ShouldBeTrue();
secretsResponse["us-west-1/org/xpto/secretabc"].ShouldBe("abc123");
}
[Fact]
@ -138,13 +138,13 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test_key");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
// Create Response & Respond
var secrets = new Dictionary<string, string>
@ -155,11 +155,11 @@ namespace Dapr.Client.Test
var secretsResponse = await SendResponseWithSecrets(secrets, request);
// Get response and validate
secretsResponse.Count.Should().Be(2);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"].Should().Be("Guess_Redis");
secretsResponse.ContainsKey("kafka_secret").Should().BeTrue();
secretsResponse["kafka_secret"].Should().Be("Guess_Kafka");
secretsResponse.Count.ShouldBe(2);
secretsResponse.ContainsKey("redis_secret").ShouldBeTrue();
secretsResponse["redis_secret"].ShouldBe("Guess_Redis");
secretsResponse.ContainsKey("kafka_secret").ShouldBeTrue();
secretsResponse["kafka_secret"].ShouldBe("Guess_Kafka");
}
[Fact]
@ -220,12 +220,12 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
}
[Fact]
@ -244,12 +244,12 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
// Create Response & Respond
var secrets = new Dictionary<string, string>();
@ -257,9 +257,9 @@ namespace Dapr.Client.Test
var secretsResponse = await SendBulkResponseWithSecrets(secrets, request);
// Get response and validate
secretsResponse.Count.Should().Be(1);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis");
secretsResponse.Count.ShouldBe(1);
secretsResponse.ContainsKey("redis_secret").ShouldBeTrue();
secretsResponse["redis_secret"]["redis_secret"].ShouldBe("Guess_Redis");
}
[Fact]
@ -279,12 +279,12 @@ namespace Dapr.Client.Test
// Get Request and validate
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");
envelope.StoreName.ShouldBe("testStore");
envelope.Metadata.Count.ShouldBe(2);
envelope.Metadata.Keys.Contains("key1").ShouldBeTrue();
envelope.Metadata.Keys.Contains("key2").ShouldBeTrue();
envelope.Metadata["key1"].ShouldBe("value1");
envelope.Metadata["key2"].ShouldBe("value2");
// Create Response & Respond
var secrets = new Dictionary<string, string>();
@ -293,11 +293,11 @@ namespace Dapr.Client.Test
var secretsResponse = await SendBulkResponseWithSecrets(secrets, request);
// Get response and validate
secretsResponse.Count.Should().Be(2);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis");
secretsResponse.ContainsKey("kafka_secret").Should().BeTrue();
secretsResponse["kafka_secret"]["kafka_secret"].Should().Be("Guess_Kafka");
secretsResponse.Count.ShouldBe(2);
secretsResponse.ContainsKey("redis_secret").ShouldBeTrue();
secretsResponse["redis_secret"]["redis_secret"].ShouldBe("Guess_Redis");
secretsResponse.ContainsKey("kafka_secret").ShouldBeTrue();
secretsResponse["kafka_secret"]["kafka_secret"].ShouldBe("Guess_Kafka");
}
[Fact]

View File

@ -18,12 +18,12 @@ 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 Moq;
using StateConsistency = Dapr.Client.Autogen.Grpc.v1.StateOptions.Types.StateConsistency;
using StateConcurrency = Dapr.Client.Autogen.Grpc.v1.StateOptions.Types.StateConcurrency;
using Shouldly;
using Xunit;
using System.Threading;
using System.Net.Http;
@ -48,8 +48,8 @@ namespace Dapr.Client.Test
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Size.Should().Be("small");
state.Color.Should().Be("yellow");
state.Size.ShouldBe("small");
state.Color.ShouldBe("yellow");
}
[Fact]
@ -66,7 +66,7 @@ namespace Dapr.Client.Test
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Should().HaveCount(1);
state.Count.ShouldBe(1);
}
[Fact]
@ -85,9 +85,9 @@ namespace Dapr.Client.Test
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Should().HaveCount(1);
state[0].Value.Size.Should().Match(size);
state[0].Value.Color.Should().Match(color);
state.Count.ShouldBe(1);
state[0].Value.Size.ShouldMatch(size);
state[0].Value.Color.ShouldMatch(color);
}
[Fact]
@ -122,8 +122,8 @@ namespace Dapr.Client.Test
// Create Response & Validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetBulkStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Metadata.Should().BeEquivalentTo(metadata);
envelope.StoreName.ShouldBe("testStore");
envelope.Metadata.ShouldBe(metadata);
}
[Fact]
@ -139,9 +139,9 @@ namespace Dapr.Client.Test
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");
state.Size.ShouldBe("small");
state.Color.ShouldBe("yellow");
etag.ShouldBe("Test_Etag");
}
[Fact]
@ -191,7 +191,7 @@ namespace Dapr.Client.Test
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Should().BeNull();
state.ShouldBeNull();
}
[Theory]
@ -205,15 +205,15 @@ namespace Dapr.Client.Test
// 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);
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
envelope.Consistency.ShouldBe(expectedConsistencyMode);
// Create Response & Respond
var state = await request.CompleteWithMessageAsync(MakeGetStateResponse<Widget>(null));
// Get response and validate
state.Should().BeNull();
state.ShouldBeNull();
}
[Fact]
@ -229,15 +229,15 @@ namespace Dapr.Client.Test
// 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);
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
envelope.Metadata.ShouldBe(metadata);
// Create Response & Respond
var state = await request.CompleteWithMessageAsync(MakeGetStateResponse<Widget>(null));
// Get response and validate
state.Should().BeNull();
state.ShouldBeNull();
}
[Fact]
@ -291,15 +291,15 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
state.Key.ShouldBe("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);
stateFromRequest.Size.ShouldBe(widget.Size);
stateFromRequest.Color.ShouldBe(widget.Color);
}
[Fact]
@ -329,20 +329,20 @@ namespace Dapr.Client.Test
// Create Response & Validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(3);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(3);
envelope.States[0].Key.Should().Be("testKey1");
envelope.States[0].Value.Should().Equal(ByteString.CopyFromUtf8(JsonSerializer.Serialize("testValue1")));
envelope.States[0].Metadata.Should().ContainKey("partitionKey1");
envelope.States[0].Key.ShouldBe("testKey1");
envelope.States[0].Value.ShouldBe(ByteString.CopyFromUtf8(JsonSerializer.Serialize("testValue1")));
envelope.States[0].Metadata.ShouldContainKey("partitionKey1");
envelope.States[1].Key.Should().Be("testKey2");
envelope.States[1].Value.Should().Equal(ByteString.CopyFromUtf8(JsonSerializer.Serialize("testValue2")));
envelope.States[1].Metadata.Should().ContainKey("partitionKey2");
envelope.States[1].Key.ShouldBe("testKey2");
envelope.States[1].Value.ShouldBe(ByteString.CopyFromUtf8(JsonSerializer.Serialize("testValue2")));
envelope.States[1].Metadata.ShouldContainKey("partitionKey2");
envelope.States[2].Key.Should().Be("testKey3");
envelope.States[2].Value.Should().Equal(ByteString.CopyFromUtf8(JsonSerializer.Serialize("testValue3")));
envelope.States[2].Metadata.Should().ContainKey("partitionKey3");
envelope.States[2].Key.ShouldBe("testKey3");
envelope.States[2].Value.ShouldBe(ByteString.CopyFromUtf8(JsonSerializer.Serialize("testValue3")));
envelope.States[2].Metadata.ShouldContainKey("partitionKey3");
}
[Fact]
@ -374,11 +374,11 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
state.Value.Should().Equal(ByteString.Empty);
state.Key.ShouldBe("test");
state.Value.ShouldBe(ByteString.Empty);
}
[Fact]
@ -453,34 +453,34 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.ExecuteStateTransactionRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Operations.Count.Should().Be(3);
envelope.StoreName.ShouldBe("testStore");
envelope.Operations.Count.ShouldBe(3);
var req1 = envelope.Operations[0];
req1.Request.Key.Should().Be("stateKey1");
req1.OperationType.Should().Be(StateOperationType.Upsert.ToString().ToLower());
req1.Request.Key.ShouldBe("stateKey1");
req1.OperationType.ShouldBe(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(StateConcurrency.ConcurrencyLastWrite);
value1.Size.ShouldBe(stateValue1.Size);
value1.Color.ShouldBe(stateValue1.Color);
req1.Request.Etag.Value.ShouldBe("testEtag");
req1.Request.Metadata.Count.ShouldBe(1);
req1.Request.Metadata["a"].ShouldBe("b");
req1.Request.Options.Concurrency.ShouldBe(StateConcurrency.ConcurrencyLastWrite);
var req2 = envelope.Operations[1];
req2.Request.Key.Should().Be("stateKey2");
req2.OperationType.Should().Be(StateOperationType.Delete.ToString().ToLower());
req2.Request.Key.ShouldBe("stateKey2");
req2.OperationType.ShouldBe(StateOperationType.Delete.ToString().ToLower());
var valueJson2 = req2.Request.Value.ToStringUtf8();
var value2 = JsonSerializer.Deserialize<int>(valueJson2, client.InnerClient.JsonSerializerOptions);
value2.Should().Be(100);
value2.ShouldBe(100);
var req3 = envelope.Operations[2];
req3.Request.Key.Should().Be("stateKey3");
req3.OperationType.Should().Be(StateOperationType.Upsert.ToString().ToLower());
req3.Request.Key.ShouldBe("stateKey3");
req3.OperationType.ShouldBe(StateOperationType.Upsert.ToString().ToLower());
var valueJson3 = req3.Request.Value.ToStringUtf8();
var value3 = JsonSerializer.Deserialize<string>(valueJson3, client.InnerClient.JsonSerializerOptions);
value3.Should().Be("teststring");
value3.ShouldBe("teststring");
}
[Fact]
@ -541,8 +541,8 @@ namespace Dapr.Client.Test
request.Dismiss();
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
}
[Fact]
@ -590,8 +590,8 @@ namespace Dapr.Client.Test
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Value.Size.Should().Be("small");
state.Value.Color.Should().Be("yellow");
state.Value.Size.ShouldBe("small");
state.Value.Color.ShouldBe("yellow");
}
[Fact]
@ -605,8 +605,8 @@ namespace Dapr.Client.Test
var envelope = MakeGetStateResponse<Widget>(null);
var state = await request.CompleteWithMessageAsync(envelope);
state.Key.Should().Be("test");
state.Value.Should().BeNull();
state.Key.ShouldBe("test");
state.Value.ShouldBeNull();
}
[Fact]
@ -620,9 +620,9 @@ namespace Dapr.Client.Test
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.Key.ShouldBe("test");
state.Value.Size.ShouldBe("small");
state.Value.Color.ShouldBe("yellow");
// Modify the state and save it
state.Value.Color = "green";
@ -637,15 +637,15 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request2.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(1);
var requestState = envelope.States[0];
requestState.Key.Should().Be("test");
requestState.Key.ShouldBe("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");
stateFromRequest.Size.ShouldBe("small");
stateFromRequest.Color.ShouldBe("green");
}
[Fact]
@ -659,9 +659,9 @@ namespace Dapr.Client.Test
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.Key.ShouldBe("test");
state.Value.Size.ShouldBe("small");
state.Value.Color.ShouldBe("yellow");
state.Value.Color = "green";
var request2 = await client.CaptureGrpcRequestAsync(async daprClient =>
@ -673,8 +673,8 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
}
@ -713,22 +713,22 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(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);
state.Key.ShouldBe("test");
state.Metadata.Count.ShouldBe(2);
state.Metadata.Keys.Contains("key1").ShouldBeTrue();
state.Metadata.Keys.Contains("key2").ShouldBeTrue();
state.Metadata["key1"].ShouldBe("value1");
state.Metadata["key2"].ShouldBe("value2");
state.Options.Concurrency.ShouldBe(expectedConcurrency);
state.Options.Consistency.ShouldBe(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);
stateFromRequest.Size.ShouldBe(widget.Size);
stateFromRequest.Color.ShouldBe(widget.Color);
}
[Theory]
@ -762,22 +762,22 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(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);
state.Etag.Value.ShouldBe("Test_Etag");
state.Metadata.Count.ShouldBe(2);
state.Metadata.Keys.Contains("key1").ShouldBeTrue();
state.Metadata.Keys.Contains("key2").ShouldBeTrue();
state.Metadata["key1"].ShouldBe("value1");
state.Metadata["key2"].ShouldBe("value2");
state.Options.Concurrency.ShouldBe(expectedConcurrency);
state.Options.Consistency.ShouldBe(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);
stateFromRequest.Size.ShouldBe(widget.Size);
stateFromRequest.Color.ShouldBe(widget.Color);
}
[Fact]
@ -827,8 +827,7 @@ namespace Dapr.Client.Test
await client.CallStateApi<string>()
.Build();
await FluentActions.Awaiting(async () => await client.DaprClient.TrySaveStateAsync("test", "test", "testValue", null))
.Should().ThrowAsync<ArgumentException>();
await Should.ThrowAsync<ArgumentException>(async () => await client.DaprClient.TrySaveStateAsync("test", "test", "testValue", null));
}
[Fact]
@ -877,8 +876,7 @@ namespace Dapr.Client.Test
await client.CallStateApi<string>()
.Build();
await FluentActions.Awaiting(async () => await client.DaprClient.TryDeleteStateAsync("test", "test", null))
.Should().ThrowAsync<ArgumentException>();
await Should.ThrowAsync<ArgumentException>(async () => await client.DaprClient.TryDeleteStateAsync("test", "test", null));
}
[Fact]
@ -943,10 +941,10 @@ namespace Dapr.Client.Test
// 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);
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
envelope.Options.Concurrency.ShouldBe(expectedConcurrency);
envelope.Options.Consistency.ShouldBe(expectedConsistency);
}
@ -975,11 +973,11 @@ namespace Dapr.Client.Test
// 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);
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
envelope.Etag.Value.ShouldBe("Test_Etag");
envelope.Options.Concurrency.ShouldBe(expectedConcurrency);
envelope.Options.Consistency.ShouldBe(expectedConsistency);
}
[Fact]
@ -1003,10 +1001,10 @@ namespace Dapr.Client.Test
// 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");
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(1);
envelope.States[0].Key.ShouldBe(key);
envelope.States[0].Metadata.ShouldContainKey("partitionKey");
}
[Fact]
@ -1019,9 +1017,9 @@ namespace Dapr.Client.Test
// Validate request.
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.QueryStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Query.Should().Be(queryJson);
envelope.Metadata.Should().BeEmpty();
envelope.StoreName.ShouldBe("testStore");
envelope.Query.ShouldBe(queryJson);
envelope.Metadata.ShouldBeEmpty();
// Validate response.
var testData = new Widget() { Color = "Green", Size = "Small" };
@ -1029,11 +1027,11 @@ namespace Dapr.Client.Test
wireResponse.Results.Add(MakeQueryStateItem("test", testData, "an etag"));
var response = await request.CompleteWithMessageAsync(wireResponse);
response.Results.Count.Should().Be(1);
response.Results[0].Key.Should().Be("test");
response.Results[0].Data.Should().Be(testData);
response.Results[0].ETag.Should().Be("an etag");
response.Results[0].Error.Should().BeNullOrEmpty();
response.Results.Count.ShouldBe(1);
response.Results[0].Key.ShouldBe("test");
response.Results[0].Data.ShouldBe(testData);
response.Results[0].ETag.ShouldBe("an etag");
response.Results[0].Error.ShouldBeNullOrEmpty();
}
[Fact]
@ -1046,9 +1044,9 @@ namespace Dapr.Client.Test
// Validate request.
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.QueryStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Query.Should().Be(queryJson);
envelope.Metadata.Should().BeEmpty();
envelope.StoreName.ShouldBe("testStore");
envelope.Query.ShouldBe(queryJson);
envelope.Metadata.ShouldBeEmpty();
// Validate response, we expect to only get the first object as the 2nd will present an error.
var testData1 = new Widget() { Color = "Green", Size = "Small" };
@ -1061,21 +1059,21 @@ namespace Dapr.Client.Test
wireResponse.Results.Add(MakeQueryStateItem("test3", testData3));
var ex = await Assert.ThrowsAsync<StateQueryException<Widget>>(() => request.CompleteWithMessageAsync(wireResponse));
ex.Message.Should().Be("Encountered an error while processing state query results.");
ex.Message.ShouldBe("Encountered an error while processing state query results.");
var response = ex.Response;
response.Results.Count.Should().Be(2);
response.Results[0].Key.Should().Be("test1");
response.Results[0].Data.Should().Be(testData1);
response.Results[0].ETag.Should().BeNullOrEmpty();
response.Results[0].Error.Should().BeNullOrEmpty();
response.Results[1].Key.Should().Be("test3");
response.Results[1].Data.Should().Be(testData3);
response.Results[1].ETag.Should().BeNullOrEmpty();
response.Results[1].Error.Should().BeNullOrEmpty();
response.Results.Count.ShouldBe(2);
response.Results[0].Key.ShouldBe("test1");
response.Results[0].Data.ShouldBe(testData1);
response.Results[0].ETag.ShouldBeNullOrEmpty();
response.Results[0].Error.ShouldBeNullOrEmpty();
response.Results[1].Key.ShouldBe("test3");
response.Results[1].Data.ShouldBe(testData3);
response.Results[1].ETag.ShouldBeNullOrEmpty();
response.Results[1].Error.ShouldBeNullOrEmpty();
var failedKeys = ex.FailedKeys;
failedKeys.Count.Should().Be(1);
failedKeys[0].Should().Be("test2");
failedKeys.Count.ShouldBe(1);
failedKeys[0].ShouldBe("test2");
}
private Autogenerated.GetStateResponse MakeGetStateResponse<T>(T state, string etag = null)
@ -1157,20 +1155,20 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(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);
state.Key.ShouldBe("test");
state.Metadata.Count.ShouldBe(2);
state.Metadata.Keys.Contains("key1").ShouldBeTrue();
state.Metadata.Keys.Contains("key2").ShouldBeTrue();
state.Metadata["key1"].ShouldBe("value1");
state.Metadata["key2"].ShouldBe("value2");
state.Options.Concurrency.ShouldBe(expectedConcurrency);
state.Options.Consistency.ShouldBe(expectedConsistency);
var stateBinaryData = state.Value.ToStringUtf8();
stateBinaryData.Should().Be(data);
stateBinaryData.ShouldBe(data);
}
[Fact]
@ -1189,13 +1187,13 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
state.Key.ShouldBe("test");
var stateBinaryData = state.Value.ToStringUtf8();
stateBinaryData.Should().Be(data);
stateBinaryData.ShouldBe(data);
}
[Fact]
@ -1213,11 +1211,11 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
state.Value.Should().Equal(ByteString.Empty);
state.Key.ShouldBe("test");
state.Value.ShouldBe(ByteString.Empty);
}
[Fact]
@ -1265,20 +1263,20 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.StoreName.ShouldBe("testStore");
envelope.States.Count.ShouldBe(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);
state.Etag.Value.ShouldBe("Test_Etag");
state.Metadata.Count.ShouldBe(2);
state.Metadata.Keys.Contains("key1").ShouldBeTrue();
state.Metadata.Keys.Contains("key2").ShouldBeTrue();
state.Metadata["key1"].ShouldBe("value1");
state.Metadata["key2"].ShouldBe("value2");
state.Options.Concurrency.ShouldBe(expectedConcurrency);
state.Options.Consistency.ShouldBe(expectedConsistency);
var stateBinaryData = state.Value.ToStringUtf8();
stateBinaryData.Should().Be(data);
stateBinaryData.ShouldBe(data);
}
[Fact]
@ -1332,8 +1330,7 @@ namespace Dapr.Client.Test
var response = client.CallStateApi<string>()
.Build();
await FluentActions.Awaiting(async () => await client.DaprClient.TrySaveByteStateAsync("test", "test", stateBytes.AsMemory(), null))
.Should().ThrowAsync<ArgumentException>();
await Should.ThrowAsync<ArgumentException>(async () => await client.DaprClient.TrySaveByteStateAsync("test", "test", stateBytes.AsMemory(), null));
}
[Fact]
@ -1365,7 +1362,7 @@ namespace Dapr.Client.Test
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.ToArray().Should().BeNullOrEmpty();
state.ToArray().ShouldBeEmpty();
}
[Theory]
@ -1379,15 +1376,15 @@ namespace Dapr.Client.Test
// 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);
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
envelope.Consistency.ShouldBe(expectedConsistencyMode);
var binaryData = Encoding.ASCII.GetBytes("test data");
// Create Response & Respond
var state = await request.CompleteWithMessageAsync(MakeGetByteStateResponse(binaryData.AsMemory()));
var stateStr = ByteString.CopyFrom(state.Span).ToByteArray();
// Get response and validate
stateStr.Should().BeEquivalentTo(binaryData);
stateStr.ShouldBeEquivalentTo(binaryData);
}
[Fact]
@ -1400,18 +1397,17 @@ namespace Dapr.Client.Test
{ "partitionKey", "mypartition" }
};
var request = await client.CaptureGrpcRequestAsync(async daprClient => await daprClient.GetByteStateAndETagAsync("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);
envelope.StoreName.ShouldBe("testStore");
envelope.Key.ShouldBe("test");
envelope.Metadata.ShouldBe(metadata);
var binaryData = Encoding.ASCII.GetBytes("test data");
// Create Response & Respond
var (state, etag) = await request.CompleteWithMessageAsync((MakeGetByteStateResponse(binaryData.AsMemory())));
var stateStr = ByteString.CopyFrom(state.Span).ToByteArray();
// Get response and validate
stateStr.Should().BeEquivalentTo(binaryData);
stateStr.ShouldBe(binaryData);
}
[Fact]
@ -1442,8 +1438,8 @@ namespace Dapr.Client.Test
var (state, etag) = await request.CompleteWithMessageAsync(envelope);
var stateStr = ByteString.CopyFrom(state.Span).ToByteArray();
// Get response and validate
stateStr.Should().BeEquivalentTo(binaryData);
etag.Should().Be("Test_Etag");
stateStr.ShouldBeEquivalentTo(binaryData);
etag.ShouldBe("Test_Etag");
}
[Fact]

View File

@ -14,7 +14,7 @@
using System.Threading.Tasks;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
using Xunit;
using FluentAssertions;
using Shouldly;
using System;
namespace Dapr.Client.Test
@ -37,10 +37,10 @@ namespace Dapr.Client.Test
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.TryLockRequest>();
envelope.StoreName.Should().Be("redis");
envelope.ResourceId.Should().Be("resourceId");
envelope.LockOwner.Should().Be("owner1");
envelope.ExpiryInSeconds.Should().Be(1000);
envelope.StoreName.ShouldBe("redis");
envelope.ResourceId.ShouldBe("resourceId");
envelope.LockOwner.ShouldBe("owner1");
envelope.ExpiryInSeconds.ShouldBe(1000);
// Get response and validate
var invokeResponse = new Autogenerated.TryLockResponse{
@ -56,16 +56,16 @@ namespace Dapr.Client.Test
return await daprClient.Unlock(storeName, resourceId, lockOwner);
});
var unlockEnvelope = await unlockRequest.GetRequestEnvelopeAsync<Autogenerated.UnlockRequest>();
unlockEnvelope.StoreName.Should().Be("redis");
unlockEnvelope.ResourceId.Should().Be("resourceId");
unlockEnvelope.LockOwner.Should().Be("owner1");
unlockEnvelope.StoreName.ShouldBe("redis");
unlockEnvelope.ResourceId.ShouldBe("resourceId");
unlockEnvelope.LockOwner.ShouldBe("owner1");
var invokeUnlockResponse = new Autogenerated.UnlockResponse{
Status = Autogenerated.UnlockResponse.Types.Status.LockDoesNotExist
};
var domainUnlockResponse = await unlockRequest.CompleteWithMessageAsync(invokeUnlockResponse);
domainUnlockResponse.status.Should().Be(LockStatus.LockDoesNotExist);
domainUnlockResponse.status.ShouldBe(LockStatus.LockDoesNotExist);
}
}
}
}

View File

@ -14,7 +14,7 @@
namespace Dapr.Client.Test
{
using System.Text.Json;
using FluentAssertions;
using Shouldly;
using Xunit;
public class TypeConvertersTest
@ -32,9 +32,9 @@ namespace Dapr.Client.Test
var any = TypeConverters.ToJsonAny(response, options);
var type = TypeConverters.FromJsonAny<Response>(any, options);
type.Should().BeEquivalentTo(response);
any.TypeUrl.Should().Be(string.Empty);
type.Name.Should().Be("test");
type.ShouldBeEquivalentTo(response);
any.TypeUrl.ShouldBe(string.Empty);
type.Name.ShouldBe("test");
}
private class Response

View File

@ -5,7 +5,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions"/>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />

View File

@ -97,7 +97,7 @@ namespace Dapr.E2E.Test
/// <see cref="ISerializationActor"/>. (it's defined in the base of it.)
/// That why <see cref="ISerializationActor.AnotherMethod(DateTime)"/> was created,
/// so there are now more then one method.
/// </remark>
/// </remarks>
[Fact]
public async Task ActorCanSupportCustomSerializerAndCallMoreThenOneDefinedMethod()
{

View File

@ -17,7 +17,7 @@ namespace Dapr.E2E.Test
using System.Threading.Tasks;
using Dapr.Actors;
using Dapr.E2E.Test.Actors.WeaklyTypedTesting;
using FluentAssertions;
using Shouldly;
using Xunit;
public partial class E2ETests : IAsyncLifetime
@ -34,7 +34,7 @@ namespace Dapr.E2E.Test
var result = await proxy.InvokeMethodAsync<ResponseBase>(nameof(IWeaklyTypedTestingActor.GetPolymorphicResponse));
result.Should().BeOfType<DerivedResponse>().Which.DerivedProperty.Should().NotBeNullOrWhiteSpace();
result.ShouldBeOfType<DerivedResponse>().DerivedProperty.ShouldNotBeNullOrWhiteSpace();
}
#else
[Fact]
@ -48,7 +48,7 @@ namespace Dapr.E2E.Test
var result = await proxy.InvokeMethodAsync<DerivedResponse>(nameof(IWeaklyTypedTestingActor.GetPolymorphicResponse));
result.Should().BeOfType<DerivedResponse>().Which.DerivedProperty.Should().NotBeNullOrWhiteSpace();
result.ShouldBeOfType<DerivedResponse>().DerivedProperty.ShouldNotBeNullOrWhiteSpace();
}
#endif
[Fact]
@ -62,7 +62,7 @@ namespace Dapr.E2E.Test
var result = await proxy.InvokeMethodAsync<ResponseBase>(nameof(IWeaklyTypedTestingActor.GetNullResponse));
result.Should().BeNull();
result.ShouldBeNull();
}
}
}

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Net.ClientFactory" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Shouldly"/>
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>

View File

@ -45,7 +45,7 @@ namespace Dapr.E2E.Test
{
var (appPort, httpPort, grpcPort, metricsPort) = GetFreePorts();
var componentsPath = Combine(".", "..", "..", "..", "..", "..", "test", "Dapr.E2E.Test", "components");
var resourcesPath = Combine(".", "..", "..", "..", "..", "..", "test", "Dapr.E2E.Test", "components");
var configPath = Combine(".", "..", "..", "..", "..", "..", "test", "Dapr.E2E.Test", "configuration", "featureconfig.yaml");
var arguments = new List<string>()
{
@ -55,11 +55,10 @@ namespace Dapr.E2E.Test
"--dapr-http-port", httpPort.ToString(CultureInfo.InvariantCulture),
"--dapr-grpc-port", grpcPort.ToString(CultureInfo.InvariantCulture),
"--metrics-port", metricsPort.ToString(CultureInfo.InvariantCulture),
"--components-path", componentsPath,
"--resources-path", resourcesPath,
"--config", configPath,
"--log-level", "debug",
"--dapr-http-max-request-size", "32",
"--max-body-size", "8Mi"
};
if (configuration.UseAppPort)

View File

@ -5,9 +5,9 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="Shouldly" />
<PackageReference Include="xunit"/>
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>

View File

@ -16,11 +16,10 @@ using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Dapr.Client;
using Dapr;
using FluentAssertions;
using Grpc.Net.Client;
using Microsoft.Extensions.Configuration;
using Moq;
using Shouldly;
using Xunit;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -163,7 +162,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();
config["secretName"].Should().Be("secret");
config["secretName"].ShouldBe("secret");
}
[Fact]
@ -195,8 +194,8 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();
config[firstSecretKey].Should().Be(firstSecretValue);
config[secondSecretKey].Should().Be(secondSecretValue);
config[firstSecretKey].ShouldBe(firstSecretValue);
config[secondSecretKey].ShouldBe(secondSecretValue);
}
[Fact]
@ -224,8 +223,8 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();
config[firstSecretKey].Should().Be(firstSecretValue);
config[secondSecretKey].Should().Be(secondSecretValue);
config[firstSecretKey].ShouldBe(firstSecretValue);
config[secondSecretKey].ShouldBe(secondSecretValue);
}
[Fact]
@ -252,7 +251,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object)
.Build();
config[secretName].Should().Be(secretValue);
config[secretName].ShouldBe(secretValue);
}
[Fact]
@ -282,7 +281,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore(storeName, secretDescriptors, daprClient)
.Build();
config[secretName].Should().BeNull();
config[secretName].ShouldBeNull();
}
[Fact]
@ -415,7 +414,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", daprClient)
.Build();
config["secretName"].Should().Be("secret");
config["secretName"].ShouldBe("secret");
}
[Fact]
@ -442,8 +441,8 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", daprClient)
.Build();
config["first_secret"].Should().Be("secret1");
config["second_secret"].Should().Be("secret2");
config["first_secret"].ShouldBe("secret1");
config["second_secret"].ShouldBe("secret2");
}
[Fact]
@ -468,7 +467,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", new DaprSecretDescriptor[] { new DaprSecretDescriptor("secretName__value") }, daprClient)
.Build();
config["secretName:value"].Should().Be("secret");
config["secretName:value"].ShouldBe("secret");
}
[Fact]
@ -494,7 +493,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", daprClient)
.Build();
config["first_secret:value"].Should().Be("secret1");
config["first_secret:value"].ShouldBe("secret1");
}
[Fact]
@ -525,7 +524,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["secretName__value"].Should().Be("secret");
config["secretName__value"].ShouldBe("secret");
}
[Fact]
@ -556,7 +555,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["first_secret__value"].Should().Be("secret1");
config["first_secret__value"].ShouldBe("secret1");
}
[Fact]
@ -590,7 +589,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["secretName:value"].Should().Be("secret");
config["secretName:value"].ShouldBe("secret");
}
[Fact]
@ -624,7 +623,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["secretName--value"].Should().Be("secret");
config["secretName--value"].ShouldBe("secret");
}
[Fact]
@ -658,7 +657,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["secretName--value"].Should().Be("secret");
config["secretName--value"].ShouldBe("secret");
}
[Fact]
@ -714,8 +713,8 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["secretName:value"].Should().Be("secret");
config["otherSecretName:value"].Should().Be("secret");
config["secretName:value"].ShouldBe("secret");
config["otherSecretName:value"].ShouldBe("secret");
}
[Fact]
@ -743,7 +742,7 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", daprClient, new[] { "--" })
.Build();
config["first_secret:value"].Should().Be("secret1");
config["first_secret:value"].ShouldBe("secret1");
}
[Fact]
@ -772,8 +771,8 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", daprClient, new[] { "--", "≡" })
.Build();
config["first_secret:value"].Should().Be("secret1");
config["second_secret:value"].Should().Be("secret2");
config["first_secret:value"].ShouldBe("secret1");
config["second_secret:value"].ShouldBe("secret2");
}
[Fact]
@ -803,9 +802,9 @@ namespace Dapr.Extensions.Configuration.Test
.AddDaprSecretStore("store", daprClient, new[] { "--", "≡" })
.Build();
config["first_secret:value"].Should().Be("secret1");
config["second_secret:value"].Should().Be("secret2");
config["third_secret:value"].Should().Be("secret3");
config["first_secret:value"].ShouldBe("secret1");
config["second_secret:value"].ShouldBe("secret2");
config["third_secret:value"].ShouldBe("secret3");
}
[Fact]
@ -840,7 +839,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["secretName--value"].Should().Be("secret");
config["secretName--value"].ShouldBe("secret");
}
[Fact]
@ -874,7 +873,7 @@ namespace Dapr.Extensions.Configuration.Test
})
.Build();
config["first_secret--value"].Should().Be("secret1");
config["first_secret--value"].ShouldBe("secret1");
}
[Fact]

View File

@ -13,6 +13,7 @@
using System;
using System.Net.Http;
using Dapr.Client.Autogen.Grpc.v1;
using Dapr.Jobs.Models;
using Moq;
using Xunit;
@ -167,6 +168,21 @@ public sealed class DaprJobsGrpcClientTests
});
#pragma warning restore CS0618 // Type or member is obsolete
}
[Fact]
public void ShouldDeserialize_EveryExpression()
{
const string scheduleText = "@every 1m";
var response = new GetJobResponse { Job = new Job { Name = "test", Schedule = scheduleText } };
var schedule = DaprJobSchedule.FromExpression(scheduleText);
var jobDetails = DaprJobsGrpcClient.DeserializeJobResponse(response);
Assert.Null(jobDetails.Payload);
Assert.Equal(0, jobDetails.RepeatCount);
Assert.Null(jobDetails.Ttl);
Assert.Null(jobDetails.DueTime);
Assert.Equal(jobDetails.Schedule.ExpressionValue, schedule.ExpressionValue);
}
private sealed record TestPayload(string Name, string Color);
}

View File

@ -14,6 +14,9 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
@ -25,8 +28,11 @@ using Dapr.Jobs.Models.Responses;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Dapr.Jobs.Test.Extensions;
@ -40,15 +46,7 @@ public class EndpointRouteBuilderExtensionsTest
var client = server.CreateClient();
var serializedPayload = JsonSerializer.Serialize(new SamplePayload("Dapr", 789));
var serializedPayloadBytes = Encoding.UTF8.GetBytes(serializedPayload);
var jobDetails = new DaprJobDetails(new DaprJobSchedule("0 0 * * *"))
{
RepeatCount = 5,
DueTime = DateTimeOffset.UtcNow,
Ttl = DateTimeOffset.UtcNow.AddHours(1),
Payload = serializedPayloadBytes
};
var content = new StringContent(JsonSerializer.Serialize(jobDetails), Encoding.UTF8, "application/json");
var content = new StringContent(serializedPayload, Encoding.UTF8, "application/json");
const string jobName = "testJob";
var response = await client.PostAsync($"/job/{jobName}", content);
@ -68,15 +66,7 @@ public class EndpointRouteBuilderExtensionsTest
var client = server.CreateClient();
var serializedPayload = JsonSerializer.Serialize(new SamplePayload("Dapr", 789));
var serializedPayloadBytes = Encoding.UTF8.GetBytes(serializedPayload);
var jobDetails = new DaprJobDetails(new DaprJobSchedule("0 0 * * *"))
{
RepeatCount = 5,
DueTime = DateTimeOffset.UtcNow,
Ttl = DateTimeOffset.UtcNow.AddHours(1),
Payload = serializedPayloadBytes
};
var content = new StringContent(JsonSerializer.Serialize(jobDetails), Encoding.UTF8, "application/json");
var content = new StringContent(serializedPayload, Encoding.UTF8, "application/json");
const string jobName = "testJob";
var response = await client.PostAsync($"/job/{jobName}", content);
@ -88,32 +78,117 @@ public class EndpointRouteBuilderExtensionsTest
Assert.Equal(jobName, validator.JobName);
Assert.Equal(serializedPayload, validator.SerializedPayload);
}
[Fact]
public async Task MapDaprScheduledJobHandler_InvalidPayload()
public async Task MapDaprScheduledJobHandler_HandlesTimeoutCorrectly()
{
// Arrange
var server = CreateTestServer();
var client = server.CreateClient();
var timeout = TimeSpan.FromSeconds(5);
const string testJobName = "testJob";
var testJobPayload = Encoding.UTF8.GetBytes("testPayload");
var content = new StringContent("", Encoding.UTF8, "application/json");
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddLogging();
services.AddRouting();
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDaprScheduledJobHandler(async (
string jobName,
ReadOnlyMemory<byte> jobPayload,
ILogger? logger,
CancellationToken cancellationToken) =>
{
logger?.LogInformation("Received trigger invocation for job '{jobName}'", jobName);
// Act
const string jobName = "testJob";
var response = await client.PostAsync($"/job/{jobName}", content);
var deserializedPayload = Encoding.UTF8.GetString(jobPayload.Span);
logger?.LogInformation(
"Received invocation for the job '{jobName}' with payload '{deserializedPayload}'",
jobName, deserializedPayload);
await Task.Delay(TimeSpan.FromSeconds(1),
cancellationToken); //Less than the timeout, so this should work without throwing
var validator = server.Services.GetRequiredService<Validator>();
Assert.Equal(jobName, validator.JobName);
Assert.Null(validator.SerializedPayload);
return Task.CompletedTask;
}, timeout);
});
});
var testServer = new TestServer(builder);
var client = testServer.CreateClient();
var requestContent = new ByteArrayContent(testJobPayload);
var request = new HttpRequestMessage(HttpMethod.Post, $"/job/{testJobName}")
{
Content = requestContent
};
// Act & Assert
var response = await client.SendAsync(request);
Assert.True(response.IsSuccessStatusCode);
}
[Fact]
public async Task MapDaprScheduledJobHandler_AppliesTimeoutCorrectly()
{
// Arrange
var timeout = TimeSpan.FromSeconds(1);
const string testJobName = "testJob";
var testJobPayload = Encoding.UTF8.GetBytes("testPayload");
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddLogging();
services.AddRouting();
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDaprScheduledJobHandler(async (
string jobName,
ReadOnlyMemory<byte> jobPayload,
ILogger? logger,
CancellationToken cancellationToken) =>
{
logger?.LogInformation("Received trigger invocation for job '{jobName}'", jobName);
var deserializedPayload = Encoding.UTF8.GetString(jobPayload.Span);
logger?.LogInformation(
"Received invocation for the job '{jobName}' with payload '{deserializedPayload}'",
jobName, deserializedPayload);
await Task.Delay(timeout.Add(TimeSpan.FromSeconds(3)),
cancellationToken); //Intentionally delay longer than the timeout allows
return Task.CompletedTask;
}, timeout);
});
});
var testServer = new TestServer(builder);
var client = testServer.CreateClient();
var requestContent = new ByteArrayContent(testJobPayload);
var request = new HttpRequestMessage(HttpMethod.Post, $"/job/{testJobName}")
{
Content = requestContent
};
// Act & Assert
await Assert.ThrowsAsync<TaskCanceledException>(async () => await client.SendAsync(request));
}
private sealed record SamplePayload(string Name, int Count);
public sealed class Validator
{
public string? JobName { get; set; }
public string? SerializedPayload { get; set; }
}
@ -130,15 +205,10 @@ public class EndpointRouteBuilderExtensionsTest
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDaprScheduledJobHandler(async (string? jobName, DaprJobDetails? jobDetails, Validator validator, CancellationToken cancellationToken) =>
endpoints.MapDaprScheduledJobHandler(async (string jobName, ReadOnlyMemory<byte> jobPayload, Validator validator, CancellationToken cancellationToken) =>
{
if (jobName is not null)
validator.JobName = jobName;
if (jobDetails?.Payload is not null)
{
var payloadString = Encoding.UTF8.GetString(jobDetails.Payload);
validator.SerializedPayload = payloadString;
}
validator.JobName = jobName;
validator.SerializedPayload = Encoding.UTF8.GetString(jobPayload.Span);
await Task.CompletedTask;
});
});
@ -160,15 +230,12 @@ public class EndpointRouteBuilderExtensionsTest
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDaprScheduledJobHandler(async (string? jobName, Validator validator, DaprJobDetails? jobDetails) =>
endpoints.MapDaprScheduledJobHandler(async (string jobName, Validator validator, ReadOnlyMemory<byte> payload) =>
{
if (jobName is not null)
validator.JobName = jobName;
if (jobDetails?.Payload is not null)
{
var payloadString = Encoding.UTF8.GetString(jobDetails.Payload);
validator.SerializedPayload = payloadString;
}
validator.JobName = jobName;
var payloadString = Encoding.UTF8.GetString(payload.Span);
validator.SerializedPayload = payloadString;
await Task.CompletedTask;
});
});

Some files were not shown because too many files have changed in this diff Show More