Skip to content

Commit 92594b7

Browse files
committed
Add retry unit tests
1 parent 5f1160c commit 92594b7

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Net;
11+
using System.Reflection;
12+
using System.Runtime.CompilerServices;
13+
using System.Text;
14+
using System.Threading;
15+
using System.Threading.Tasks;
16+
using Azure;
17+
using Hl7.Fhir.ElementModel.Types;
18+
using Microsoft.AspNetCore.Http;
19+
using Microsoft.Azure.Cosmos;
20+
using Microsoft.Extensions.Logging.Abstractions;
21+
using Microsoft.Health.Abstractions.Exceptions;
22+
using Microsoft.Health.Core.Features.Context;
23+
using Microsoft.Health.Extensions.DependencyInjection;
24+
using Microsoft.Health.Fhir.Core.Features.Context;
25+
using Microsoft.Health.Fhir.Core.UnitTests.Extensions;
26+
using Microsoft.Health.Fhir.CosmosDb.Core.Configs;
27+
using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage;
28+
using Microsoft.Health.Fhir.CosmosDb.Features.Queries;
29+
using Microsoft.Health.Fhir.CosmosDb.Features.Storage;
30+
using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Queues;
31+
using Microsoft.Health.Fhir.Tests.Common;
32+
using Microsoft.Health.Test.Utilities;
33+
using NSubstitute;
34+
using Xunit;
35+
36+
namespace Microsoft.Health.Fhir.CosmosDb.UnitTests.Features.Storage.Queues
37+
{
38+
[Trait(Traits.OwningTeam, OwningTeam.Fhir)]
39+
[Trait(Traits.Category, Categories.DataSourceValidation)]
40+
public class CosmosQueueClientTests
41+
{
42+
private readonly ICosmosQueryFactory _cosmosQueryFactory;
43+
private readonly ICosmosDbDistributedLockFactory _distributedLockFactory;
44+
private readonly CosmosDataStoreConfiguration _cosmosDataStoreConfiguration = new CosmosDataStoreConfiguration();
45+
private readonly RequestContextAccessor<IFhirRequestContext> _requestContextAccessor;
46+
private readonly RetryExceptionPolicyFactory _retryPolicyFactory;
47+
private readonly CosmosQueueClient _cosmosQueueClient;
48+
49+
public CosmosQueueClientTests()
50+
{
51+
_cosmosQueryFactory = Substitute.For<ICosmosQueryFactory>();
52+
_distributedLockFactory = Substitute.For<ICosmosDbDistributedLockFactory>();
53+
_requestContextAccessor = Substitute.For<RequestContextAccessor<IFhirRequestContext>>();
54+
_retryPolicyFactory = new RetryExceptionPolicyFactory(_cosmosDataStoreConfiguration, _requestContextAccessor, NullLogger<RetryExceptionPolicyFactory>.Instance);
55+
56+
_cosmosQueueClient = new CosmosQueueClient(
57+
Substitute.For<Func<IScoped<Container>>>(),
58+
_cosmosQueryFactory,
59+
_distributedLockFactory,
60+
_retryPolicyFactory,
61+
NullLogger<CosmosQueueClient>.Instance);
62+
}
63+
64+
[Theory]
65+
[InlineData(HttpStatusCode.ServiceUnavailable)]
66+
[InlineData(HttpStatusCode.TooManyRequests)]
67+
[InlineData(HttpStatusCode.Gone)]
68+
[InlineData((HttpStatusCode)449)]
69+
[InlineData(HttpStatusCode.RequestTimeout)]
70+
public async Task GivenADequeueJobOperation_WhenExceptionOccurs_RetryWillHappen(HttpStatusCode statusCode)
71+
{
72+
// Arrange
73+
ICosmosQuery<JobGroupWrapper> cosmosQuery = Substitute.For<ICosmosQuery<JobGroupWrapper>>();
74+
_cosmosQueryFactory.Create<JobGroupWrapper>(Arg.Any<Container>(), Arg.Any<CosmosQueryContext>())
75+
.ReturnsForAnyArgs(cosmosQuery);
76+
77+
int callCount = 0;
78+
cosmosQuery.ExecuteNextAsync(Arg.Any<CancellationToken>()).ReturnsForAnyArgs(_ =>
79+
{
80+
if (callCount++ == 0)
81+
{
82+
throw new TestCosmosException(statusCode);
83+
}
84+
85+
return Task.FromResult(Substitute.For<FeedResponse<JobGroupWrapper>>());
86+
});
87+
88+
// Act
89+
await _cosmosQueueClient.DequeueAsync(0, "testworker", 10, CancellationToken.None);
90+
91+
// Assert
92+
Assert.Equal(2, callCount);
93+
await cosmosQuery.ReceivedWithAnyArgs(2).ExecuteNextAsync(Arg.Any<CancellationToken>());
94+
}
95+
96+
[Theory]
97+
[InlineData(typeof(CosmosException))]
98+
[InlineData(typeof(RequestRateExceededException))]
99+
public async Task GivenADequeueJobOperation_WhenExceptionWithRetryAfterIsProvided_PolicyRespectsRetryAfter(Type exceptionType)
100+
{
101+
// Arrange
102+
ICosmosQuery<JobGroupWrapper> cosmosQuery = Substitute.For<ICosmosQuery<JobGroupWrapper>>();
103+
_cosmosQueryFactory.Create<JobGroupWrapper>(Arg.Any<Container>(), Arg.Any<CosmosQueryContext>())
104+
.ReturnsForAnyArgs(cosmosQuery);
105+
var retryAfter = TimeSpan.FromSeconds(1);
106+
int callCount = 0;
107+
108+
cosmosQuery.ExecuteNextAsync(Arg.Any<CancellationToken>()).ReturnsForAnyArgs(_ =>
109+
{
110+
if (callCount++ == 0)
111+
{
112+
throw exceptionType == typeof(CosmosException)
113+
? new TestCosmosException(HttpStatusCode.TooManyRequests, retryAfter)
114+
: new RequestRateExceededException(retryAfter);
115+
}
116+
117+
return Task.FromResult(Substitute.For<FeedResponse<JobGroupWrapper>>());
118+
});
119+
120+
var stopwatch = Stopwatch.StartNew();
121+
122+
// Act
123+
await _cosmosQueueClient.DequeueAsync(0, "testworker", 10, CancellationToken.None);
124+
125+
stopwatch.Stop();
126+
127+
// Assert
128+
Assert.Equal(2, callCount);
129+
await cosmosQuery.ReceivedWithAnyArgs(2).ExecuteNextAsync(Arg.Any<CancellationToken>());
130+
Assert.True(stopwatch.Elapsed >= retryAfter, "Policy should respect the RetryAfter value.");
131+
}
132+
133+
public class TestCosmosException : CosmosException
134+
{
135+
private readonly TimeSpan? _retryAfter;
136+
137+
public TestCosmosException(HttpStatusCode statusCode, TimeSpan? retryAfter = null)
138+
: base("Test exception message", statusCode, 0, "test-activity-id", 0.0)
139+
{
140+
_retryAfter = retryAfter;
141+
}
142+
143+
public override TimeSpan? RetryAfter => _retryAfter;
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)