Skip to content

Commit 4b7496c

Browse files
committed
Add unit tests for shim entity client support
1 parent 65c92e4 commit 4b7496c

File tree

12 files changed

+665
-62
lines changed

12 files changed

+665
-62
lines changed

src/Client/Core/Entities/EntityMetadata.cs

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,15 @@ namespace Microsoft.DurableTask.Client.Entities;
1111
/// Represents entity metadata.
1212
/// </summary>
1313
/// <typeparam name="TState">The type of state held by the metadata.</typeparam>
14+
/// <remarks>
15+
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
16+
/// </remarks>
17+
/// <param name="id">The ID of the entity.</param>
1418
[JsonConverter(typeof(EntityMetadataConverter))]
15-
public class EntityMetadata<TState>
19+
public class EntityMetadata<TState>(EntityInstanceId id)
1620
{
1721
readonly TState? state;
1822

19-
/// <summary>
20-
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
21-
/// </summary>
22-
/// <param name="id">The ID of the entity.</param>
23-
public EntityMetadata(EntityInstanceId id)
24-
{
25-
this.Id = Check.NotDefault(id);
26-
this.IncludesState = false;
27-
}
28-
2923
/// <summary>
3024
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
3125
/// </summary>
@@ -41,7 +35,7 @@ public EntityMetadata(EntityInstanceId id, TState? state)
4135
/// <summary>
4236
/// Gets the ID for this entity.
4337
/// </summary>
44-
public EntityInstanceId Id { get; }
38+
public EntityInstanceId Id { get; } = Check.NotDefault(id);
4539

4640
/// <summary>
4741
/// Gets the time the entity was last modified.
@@ -64,9 +58,9 @@ public EntityMetadata(EntityInstanceId id, TState? state)
6458
/// <remarks>
6559
/// Queries can exclude the state of the entity from the metadata that is retrieved.
6660
/// </remarks>
67-
[MemberNotNullWhen(true, "State")]
68-
[MemberNotNullWhen(true, "state")]
69-
public bool IncludesState { get; }
61+
[MemberNotNullWhen(true, nameof(State))]
62+
[MemberNotNullWhen(true, nameof(state))]
63+
public bool IncludesState { get; } = false;
7064

7165
/// <summary>
7266
/// Gets the state for this entity.
@@ -96,16 +90,13 @@ public TState State
9690
/// <summary>
9791
/// Represents the metadata for a durable entity instance.
9892
/// </summary>
93+
/// <remarks>
94+
/// Initializes a new instance of the <see cref="EntityMetadata"/> class.
95+
/// </remarks>
96+
/// <param name="id">The ID of the entity.</param>
97+
/// <param name="state">The state of this entity.</param>
9998
[JsonConverter(typeof(EntityMetadataConverter))]
100-
public sealed class EntityMetadata : EntityMetadata<SerializedData>
99+
public sealed class EntityMetadata(EntityInstanceId id, SerializedData? state = null)
100+
: EntityMetadata<SerializedData>(id, state)
101101
{
102-
/// <summary>
103-
/// Initializes a new instance of the <see cref="EntityMetadata"/> class.
104-
/// </summary>
105-
/// <param name="id">The ID of the entity.</param>
106-
/// <param name="state">The state of this entity.</param>
107-
public EntityMetadata(EntityInstanceId id, SerializedData? state = null)
108-
: base(id, state)
109-
{
110-
}
111102
}

src/Client/Core/SerializedData.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,22 @@ namespace Microsoft.DurableTask.Client;
88
/// <summary>
99
/// Gets a type representing serialized data.
1010
/// </summary>
11-
public sealed class SerializedData
11+
/// <remarks>
12+
/// Initializes a new instance of the <see cref="SerializedData"/> class.
13+
/// </remarks>
14+
/// <param name="data">The serialized data.</param>
15+
/// <param name="converter">The data converter.</param>
16+
public sealed class SerializedData(string data, DataConverter? converter = null)
1217
{
13-
/// <summary>
14-
/// Initializes a new instance of the <see cref="SerializedData"/> class.
15-
/// </summary>
16-
/// <param name="data">The serialized data.</param>
17-
/// <param name="converter">The data converter.</param>
18-
public SerializedData(string data, DataConverter? converter = null)
19-
{
20-
this.Value = Check.NotNull(data);
21-
this.Converter = converter ?? JsonDataConverter.Default;
22-
}
23-
2418
/// <summary>
2519
/// Gets the serialized value.
2620
/// </summary>
27-
public string Value { get; }
21+
public string Value { get; } = Check.NotNull(data);
2822

2923
/// <summary>
3024
/// Gets the data converter.
3125
/// </summary>
32-
public DataConverter Converter { get; }
26+
public DataConverter Converter { get; } = converter ?? JsonDataConverter.Default;
3327

3428
/// <summary>
3529
/// Deserializes the data into <typeparamref name="T"/>.

src/Client/OrchestrationServiceClientShim/ShimDurableEntityClient.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ public override AsyncPageable<EntityMetadata<T>> GetAllEntitiesAsync<T>(EntityQu
6060
public override async Task<EntityMetadata?> GetEntityAsync(
6161
EntityInstanceId id, bool includeState = true, CancellationToken cancellation = default)
6262
=> this.Convert(await this.Queries.GetEntityAsync(
63-
new EntityId(id.Name, id.Key), includeState, false, cancellation));
63+
id.ConvertToCore(), includeState, false, cancellation));
6464

6565
/// <inheritdoc/>
6666
public override async Task<EntityMetadata<T>?> GetEntityAsync<T>(
6767
EntityInstanceId id, bool includeState = true, CancellationToken cancellation = default)
6868
=> this.Convert<T>(await this.Queries.GetEntityAsync(
69-
new EntityId(id.Name, id.Key), includeState, false, cancellation));
69+
id.ConvertToCore(), includeState, false, cancellation));
7070

7171
/// <inheritdoc/>
7272
public override async Task SignalEntityAsync(
@@ -108,6 +108,7 @@ AsyncPageable<TMetadata> GetAllEntitiesAsync<TMetadata>(
108108

109109
return Pageable.Create(async (continuation, size, cancellation) =>
110110
{
111+
continuation ??= filter?.ContinuationToken;
111112
size ??= filter?.PageSize;
112113
EntityBackendQueries.EntityQueryResult result = await this.Queries.QueryEntitiesAsync(
113114
new EntityBackendQueries.EntityQuery()
@@ -129,7 +130,7 @@ AsyncPageable<TMetadata> GetAllEntitiesAsync<TMetadata>(
129130
EntityMetadata<T> Convert<T>(EntityBackendQueries.EntityMetadata metadata)
130131
{
131132
return new(
132-
new EntityInstanceId(metadata.EntityId.Name, metadata.EntityId.Key),
133+
metadata.EntityId.ConvertFromCore(),
133134
this.Converter.Deserialize<T>(metadata.SerializedState))
134135
{
135136
LastModifiedTime = metadata.LastModifiedTime,

src/Client/OrchestrationServiceClientShim/ShimDurableTaskClient.cs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ namespace Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
1717
/// <summary>
1818
/// A shim client for interacting with the backend via <see cref="Core.IOrchestrationServiceClient" />.
1919
/// </summary>
20-
class ShimDurableTaskClient : DurableTaskClient
20+
/// <remarks>
21+
/// Initializes a new instance of the <see cref="ShimDurableTaskClient"/> class.
22+
/// </remarks>
23+
/// <param name="name">The name of the client.</param>
24+
/// <param name="options">The client options.</param>
25+
class ShimDurableTaskClient(string name, ShimDurableTaskClientOptions options) : DurableTaskClient(name)
2126
{
22-
readonly ShimDurableTaskClientOptions options;
27+
readonly ShimDurableTaskClientOptions options = Check.NotNull(options);
2328
ShimDurableEntityClient? entities;
2429

2530
/// <summary>
@@ -34,17 +39,6 @@ public ShimDurableTaskClient(
3439
{
3540
}
3641

37-
/// <summary>
38-
/// Initializes a new instance of the <see cref="ShimDurableTaskClient"/> class.
39-
/// </summary>
40-
/// <param name="name">The name of the client.</param>
41-
/// <param name="options">The client options.</param>
42-
public ShimDurableTaskClient(string name, ShimDurableTaskClientOptions options)
43-
: base(name)
44-
{
45-
this.options = Check.NotNull(options);
46-
}
47-
4842
/// <inheritdoc/>
4943
public override DurableEntityClient Entities
5044
{

src/Client/OrchestrationServiceClientShim/ShimExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT License.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using DurableTask.Core.Entities;
6+
using Microsoft.DurableTask.Entities;
57
using Core = DurableTask.Core;
68

79
namespace Microsoft.DurableTask.Client;
@@ -109,4 +111,20 @@ public static Core.OrchestrationStatus ConvertToCore(this OrchestrationRuntimeSt
109111
return new Core.PurgeInstanceFilter(
110112
(filter.CreatedFrom ?? default).UtcDateTime, filter.CreatedTo?.UtcDateTime, statuses);
111113
}
114+
115+
/// <summary>
116+
/// Convert <see cref="EntityId" /> to <see cref="EntityInstanceId" />.
117+
/// </summary>
118+
/// <param name="entityId">The entity ID to convert.</param>
119+
/// <returns>The converted entity instance ID.</returns>
120+
public static EntityInstanceId ConvertFromCore(this EntityId entityId)
121+
=> new(entityId.Name, entityId.Key);
122+
123+
/// <summary>
124+
/// Convert <see cref="EntityInstanceId" /> to <see cref="EntityId" />.
125+
/// </summary>
126+
/// <param name="entityId">The entity instance ID to convert.</param>
127+
/// <returns>The converted entity ID.</returns>
128+
public static EntityId ConvertToCore(this EntityInstanceId entityId)
129+
=> new(entityId.Name, entityId.Key);
112130
}

test/Client/OrchestrationServiceClientShim.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using DurableTask.Core;
5+
using DurableTask.Core.Entities;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Options;
78

@@ -85,4 +86,139 @@ public void UseOrchestrationService_Callback_Sets()
8586

8687
options.Client.Should().Be(client);
8788
}
89+
90+
[Fact]
91+
public void EnableEntities_NoBackendSupport_Throws()
92+
{
93+
ServiceCollection services = new();
94+
IOrchestrationServiceClient client = Mock.Of<IOrchestrationServiceClient>();
95+
services.AddSingleton(client);
96+
DefaultDurableTaskClientBuilder builder = new(null, services);
97+
98+
builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
99+
100+
IServiceProvider provider = services.BuildServiceProvider();
101+
Action act = () => provider.GetOptions<ShimDurableTaskClientOptions>();
102+
act.Should().ThrowExactly<OptionsValidationException>()
103+
.WithMessage("ShimDurableTaskClientOptions.Entities.Queries must not be null when entity support is enabled.");
104+
}
105+
106+
[Fact]
107+
public void EnableEntities_BackendSupport_ExplicitProvider()
108+
{
109+
ServiceCollection services = new();
110+
IOrchestrationServiceClient client = Mock.Of<IOrchestrationServiceClient>();
111+
services.AddSingleton(client);
112+
Mock<EntityBackendQueries> mock = new();
113+
DefaultDurableTaskClientBuilder builder = new(null, services);
114+
115+
builder.UseOrchestrationService(o =>
116+
{
117+
o.EnableEntitySupport = true;
118+
o.Entities.Queries = mock.Object;
119+
});
120+
121+
IServiceProvider provider = services.BuildServiceProvider();
122+
provider.GetOptions<ShimDurableTaskClientOptions>(); // no-throw
123+
}
124+
125+
[Fact]
126+
public void EnableEntities_BackendSupport_RegisteredService1()
127+
{
128+
ServiceCollection services = new();
129+
IOrchestrationServiceClient client = Mock.Of<IOrchestrationServiceClient>();
130+
services.AddSingleton(client);
131+
EntityBackendQueries queries = Mock.Of<EntityBackendQueries>();
132+
services.AddSingleton(queries);
133+
DefaultDurableTaskClientBuilder builder = new(null, services);
134+
135+
builder.UseOrchestrationService(o => o.EnableEntitySupport = true );
136+
137+
IServiceProvider provider = services.BuildServiceProvider();
138+
ShimDurableTaskClientOptions options = provider.GetOptions<ShimDurableTaskClientOptions>();
139+
options.Entities.Queries.Should().Be(queries);
140+
}
141+
142+
[Fact]
143+
public void EnableEntities_BackendSupport_RegisteredService2()
144+
{
145+
ServiceCollection services = new();
146+
Mock<IOrchestrationServiceClient> client = new();
147+
EntityBackendQueries queries = Mock.Of<EntityBackendQueries>();
148+
Mock<IEntityOrchestrationService> entities = client.As<IEntityOrchestrationService>();
149+
entities.Setup(m => m.EntityBackendQueries).Returns(queries);
150+
services.AddSingleton(client.Object);
151+
152+
DefaultDurableTaskClientBuilder builder = new(null, services);
153+
154+
builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
155+
156+
IServiceProvider provider = services.BuildServiceProvider();
157+
ShimDurableTaskClientOptions options = provider.GetOptions<ShimDurableTaskClientOptions>();
158+
options.Entities.Queries.Should().Be(queries);
159+
}
160+
161+
[Fact]
162+
public void EnableEntities_BackendSupport_RegisteredService3()
163+
{
164+
ServiceCollection services = new();
165+
Mock<IOrchestrationServiceClient> client = new();
166+
EntityBackendQueries queries = Mock.Of<EntityBackendQueries>();
167+
Mock<IEntityOrchestrationService> entities = client.As<IEntityOrchestrationService>();
168+
entities.Setup(m => m.EntityBackendQueries).Returns(queries);
169+
170+
DefaultDurableTaskClientBuilder builder = new(null, services);
171+
172+
builder.UseOrchestrationService(o =>
173+
{
174+
o.Client = client.Object;
175+
o.EnableEntitySupport = true;
176+
});
177+
178+
IServiceProvider provider = services.BuildServiceProvider();
179+
ShimDurableTaskClientOptions options = provider.GetOptions<ShimDurableTaskClientOptions>();
180+
options.Entities.Queries.Should().Be(queries);
181+
}
182+
183+
[Fact]
184+
public void EnableEntities_BackendSupport_RegisteredService4()
185+
{
186+
ServiceCollection services = new();
187+
IOrchestrationServiceClient client = Mock.Of<IOrchestrationServiceClient>();
188+
services.AddSingleton(client);
189+
190+
EntityBackendQueries queries = Mock.Of<EntityBackendQueries>();
191+
IEntityOrchestrationService entities = Mock.Of<IEntityOrchestrationService>(m => m.EntityBackendQueries == queries);
192+
services.AddSingleton(entities);
193+
194+
DefaultDurableTaskClientBuilder builder = new(null, services);
195+
196+
builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
197+
198+
IServiceProvider provider = services.BuildServiceProvider();
199+
ShimDurableTaskClientOptions options = provider.GetOptions<ShimDurableTaskClientOptions>();
200+
options.Entities.Queries.Should().Be(queries);
201+
}
202+
203+
[Fact]
204+
public void EnableEntities_BackendSupport_RegisteredService5()
205+
{
206+
ServiceCollection services = new();
207+
IOrchestrationServiceClient client = Mock.Of<IOrchestrationServiceClient>();
208+
services.AddSingleton(client);
209+
210+
EntityBackendQueries queries = Mock.Of<EntityBackendQueries>();
211+
Mock<IOrchestrationService> orchestration = new();
212+
Mock<IEntityOrchestrationService> entities = orchestration.As<IEntityOrchestrationService>();
213+
entities.Setup(m => m.EntityBackendQueries).Returns(queries);
214+
services.AddSingleton(orchestration.Object);
215+
216+
DefaultDurableTaskClientBuilder builder = new(null, services);
217+
218+
builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
219+
220+
IServiceProvider provider = services.BuildServiceProvider();
221+
ShimDurableTaskClientOptions options = provider.GetOptions<ShimDurableTaskClientOptions>();
222+
options.Entities.Queries.Should().Be(queries);
223+
}
88224
}

0 commit comments

Comments
 (0)