Skip to content

Commit

Permalink
Merge pull request #99 from wemogy/main
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
SebastianKuesters authored Jul 26, 2024
2 parents e67e395 + f7984a3 commit 7892c3a
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 13 deletions.
5 changes: 3 additions & 2 deletions src/core/Wemogy.CQRS.UnitTests/DependencyInjectionTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -25,15 +26,15 @@ public async Task CallingAddCQRSMultipleTimesInDifferentAssembliesShouldWork()
var commands = serviceProvider.GetRequiredService<ICommands>();
var helloAssemblyACommand = new PrintHelloAssemblyACommand();
var helloAssemblyBCommand = new PrintHelloAssemblyBCommand();
var trackUserLoginCommand = new TrackUserLoginCommand("test-user-id");
var trackUserLoginCommand = new TrackUserLoginCommand(Guid.NewGuid().ToString());

// Act
var trackUserLoginCommandException = await Record.ExceptionAsync(() => commands.RunAsync(trackUserLoginCommand));
var helloAssemblyACommandException = await Record.ExceptionAsync(() => commands.RunAsync(helloAssemblyACommand));
var helloAssemblyBCommandException = await Record.ExceptionAsync(() => commands.RunAsync(helloAssemblyBCommand));

// Assert
TrackUserLoginCommandHandler.CallCount.Should().Be(1);
TrackUserLoginCommandHandler.ExecutedCount[trackUserLoginCommand.UserId].Should().Be(1);
trackUserLoginCommandException.Should().BeNull();
PrintHelloAssemblyACommandHandler.CallCount.Should().Be(1);
helloAssemblyACommandException.Should().BeNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Wemogy.CQRS.Commands.Abstractions;

namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.TrackUserLogin;

public class TrackUserLoginCommandHandler : ICommandHandler<TrackUserLoginCommand>
{
public static int CallCount { get; private set; }
public static Dictionary<string, int> ExecutedCount { get; } = new ();

public Task HandleAsync(TrackUserLoginCommand command)
{
CallCount++;
if (ExecutedCount.TryGetValue(command.UserId, out var count))
{
ExecutedCount[command.UserId] = count + 1;
}
else
{
ExecutedCount[command.UserId] = 1;
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Net;
using System.Text.Json;
using FluentAssertions;
using Moq;
using RestSharp;
using Wemogy.CQRS.Common.ValueObjects;
using Wemogy.CQRS.Extensions.FastEndpoints.Common;
using Wemogy.CQRS.Extensions.FastEndpoints.RemoteQueryRunners;
using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Queries.RequestTestContext;
using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects;

namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.HttpRemoteQueryRunners;

public class HttpRemoteQueryRunnerTests
{
[Fact]
public async Task QueryAsync_ShouldRetryRequestAndReturnResult()
{
// Arrange
var query = new RequestTestContextQuery();
var testContext = new TestContext()
{
UserId = Guid.NewGuid().ToString()
};
var resultContent = JsonSerializer.Serialize(testContext, JsonOptions.JsonSerializerOptions);
var responses = new Queue<RestResponse>();
responses.Enqueue(new RestResponse()
{
StatusCode = HttpStatusCode.ServiceUnavailable
});
responses.Enqueue(new RestResponse()
{
StatusCode = HttpStatusCode.OK,
Content = resultContent,

// Set IsSuccessful to true
IsSuccessStatusCode = true,
ResponseStatus = ResponseStatus.Completed
});

var restClientMock = new Mock<IRestClient>();
restClientMock
.Setup(
m => m.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(responses.Dequeue);

var httpRemoteQueryRunner = new HttpRemoteQueryRunner<RequestTestContextQuery, TestContext>(restClientMock.Object, string.Empty);
var request = new QueryRequest<RequestTestContextQuery>(query, new List<CommandQueryDependency>());

// Act
var result = await httpRemoteQueryRunner.QueryAsync(request, CancellationToken.None);

// Assert
result.Should().BeEquivalentTo(testContext);
responses.Should().BeEmpty();
}

[Fact]
public async Task QueryAsync_ShouldThrowWithoutResultAfterMaxRetryReachedRequest()
{
// Arrange
var query = new RequestTestContextQuery();
var testContext = new TestContext()
{
UserId = Guid.NewGuid().ToString()
};
var restClientMock = new Mock<IRestClient>();
restClientMock
.Setup(
m => m.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(() => new RestResponse()
{
StatusCode = HttpStatusCode.ServiceUnavailable
});

var httpRemoteQueryRunner = new HttpRemoteQueryRunner<RequestTestContextQuery, TestContext>(restClientMock.Object, string.Empty);
var request = new QueryRequest<RequestTestContextQuery>(query, new List<CommandQueryDependency>());

// Act
var exception = await Record.ExceptionAsync(() => httpRemoteQueryRunner.QueryAsync(request, CancellationToken.None));

// Assert
exception.Should().NotBeNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.Net;
using System.Text.Json;
using FluentAssertions;
using Moq;
using RestSharp;
using Wemogy.CQRS.Common.ValueObjects;
using Wemogy.CQRS.Extensions.FastEndpoints.Common;
using Wemogy.CQRS.Extensions.FastEndpoints.RemoteCommandRunners;
using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.Greeting;
using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.LogTestContext;

namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.RemoteCommandRunners;

public class HttpRemoteCommandRunnerTests
{
[Fact]
public async Task RunAsync_ShouldRetryRequest()
{
// Arrange
var command = new LogTestContextCommand();
var responses = new Queue<RestResponse>();
responses.Enqueue(new RestResponse()
{
StatusCode = HttpStatusCode.ServiceUnavailable
});
responses.Enqueue(new RestResponse()
{
StatusCode = HttpStatusCode.OK,

// Set IsSuccessful to true
IsSuccessStatusCode = true,
ResponseStatus = ResponseStatus.Completed
});

var restClientMock = new Mock<IRestClient>();
restClientMock
.Setup(
m => m.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(responses.Dequeue);

var httpRemoteCommandRunner = new HttpRemoteCommandRunner<LogTestContextCommand>(restClientMock.Object, string.Empty);
var request = new CommandRequest<LogTestContextCommand>(command, new List<CommandQueryDependency>());

// Act
var exception = await Record.ExceptionAsync(() => httpRemoteCommandRunner.RunAsync(request));

// Assert
exception.Should().BeNull();
responses.Should().BeEmpty();
}

[Fact]
public async Task RunAsync_ShouldThrowAfterMaxRetryReachedRequest()
{
// Arrange
var command = new LogTestContextCommand();
var restClientMock = new Mock<IRestClient>();
restClientMock
.Setup(
m => m.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(() => new RestResponse()
{
StatusCode = HttpStatusCode.ServiceUnavailable
});

var httpRemoteCommandRunner = new HttpRemoteCommandRunner<LogTestContextCommand>(restClientMock.Object, string.Empty);
var request = new CommandRequest<LogTestContextCommand>(command, new List<CommandQueryDependency>());

// Act
var exception = await Record.ExceptionAsync(() => httpRemoteCommandRunner.RunAsync(request));

// Assert
exception.Should().NotBeNull();
}

[Fact]
public async Task RunAsync_ShouldRetryRequestAndReturnResult()
{
// Arrange
var command = new GreetingCommand("Max");
var resultContent = JsonSerializer.Serialize("Hello, Max!", JsonOptions.JsonSerializerOptions);
var responses = new Queue<RestResponse>();
responses.Enqueue(new RestResponse()
{
StatusCode = HttpStatusCode.ServiceUnavailable
});
responses.Enqueue(new RestResponse()
{
StatusCode = HttpStatusCode.OK,
Content = resultContent,

// Set IsSuccessful to true
IsSuccessStatusCode = true,
ResponseStatus = ResponseStatus.Completed
});

var restClientMock = new Mock<IRestClient>();
restClientMock
.Setup(
m => m.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(responses.Dequeue);

var httpRemoteCommandRunner = new HttpRemoteCommandRunner<GreetingCommand, string>(restClientMock.Object, string.Empty);
var request = new CommandRequest<GreetingCommand>(command, new List<CommandQueryDependency>());

// Act
var result = await httpRemoteCommandRunner.RunAsync(request);

// Assert
result.Should().Be("Hello, Max!");
responses.Should().BeEmpty();
}

[Fact]
public async Task RunAsync_ShouldThrowWithoutResultAfterMaxRetryReachedRequest()
{
// Arrange
var command = new GreetingCommand("Max");
var restClientMock = new Mock<IRestClient>();
restClientMock
.Setup(
m => m.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(() => new RestResponse()
{
StatusCode = HttpStatusCode.ServiceUnavailable
});

var httpRemoteCommandRunner = new HttpRemoteCommandRunner<GreetingCommand, string>(restClientMock.Object, string.Empty);
var request = new CommandRequest<GreetingCommand>(command, new List<CommandQueryDependency>());

// Act
var exception = await Record.ExceptionAsync(() => httpRemoteCommandRunner.RunAsync(request));

// Assert
exception.Should().NotBeNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Wemogy.CQRS.Commands.Abstractions;

namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.Greeting;

public class GreetingCommand : ICommand<string>
{
public string Name { get; }

public GreetingCommand(string name)
{
Name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Wemogy.CQRS.Commands.Abstractions;

namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.Greeting;

public class GreetingCommandHandler : ICommandHandler<GreetingCommand, string>
{
public Task<string> HandleAsync(GreetingCommand command)
{
return Task.FromResult($"Hello, {command.Name}!");
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using Polly;
using Polly.Contrib.WaitAndRetry;
using Polly.Retry;
using RestSharp;
using Wemogy.Core.Errors;
using Wemogy.Core.Extensions;
Expand All @@ -12,17 +15,25 @@ namespace Wemogy.CQRS.Extensions.FastEndpoints.RemoteCommandRunners;
public class HttpRemoteCommandRunner<TCommand> : IRemoteCommandRunner<TCommand>
where TCommand : ICommandBase
{
private readonly RestClient _restClient;
private readonly IRestClient _restClient;

/// <summary>
/// This is the sub-path of the client base path
/// </summary>
private readonly string _urlPath;
private readonly IAsyncPolicy<RestResponse> _retryPolicy;

public HttpRemoteCommandRunner(RestClient restClient, string urlPath)
public HttpRemoteCommandRunner(IRestClient restClient, string urlPath)
{
_restClient = restClient;
_urlPath = urlPath;
var retryCount = 3;
var delay = Backoff.ExponentialBackoff(
TimeSpan.FromMilliseconds(100),
retryCount);
_retryPolicy = Policy
.HandleResult<RestResponse>(x => !x.IsSuccessful)
.WaitAndRetryAsync(delay);
}

public async Task RunAsync(CommandRequest<TCommand> command)
Expand All @@ -33,7 +44,7 @@ public async Task RunAsync(CommandRequest<TCommand> command)

try
{
var response = await _restClient.ExecutePostAsync(request);
var response = await _retryPolicy.ExecuteAsync(() => _restClient.ExecutePostAsync(request));

if (!response.IsSuccessful)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.Json;
using Polly;
using Polly.Contrib.WaitAndRetry;
using RestSharp;
using Wemogy.Core.Errors;
using Wemogy.Core.Extensions;
Expand All @@ -14,17 +16,25 @@ namespace Wemogy.CQRS.Extensions.FastEndpoints.RemoteCommandRunners;
public class HttpRemoteCommandRunner<TCommand, TResult> : IRemoteCommandRunner<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly RestClient _restClient;
private readonly IRestClient _restClient;

/// <summary>
/// This is the sub-path of the client base path
/// </summary>
private readonly string _urlPath;
private readonly IAsyncPolicy<RestResponse> _retryPolicy;

public HttpRemoteCommandRunner(RestClient restClient, string urlPath)
public HttpRemoteCommandRunner(IRestClient restClient, string urlPath)
{
_restClient = restClient;
_urlPath = urlPath;
var retryCount = 3;
var delay = Backoff.ExponentialBackoff(
TimeSpan.FromMilliseconds(100),
retryCount);
_retryPolicy = Policy
.HandleResult<RestResponse>(x => !x.IsSuccessful)
.WaitAndRetryAsync(delay);
}

public async Task<TResult> RunAsync(CommandRequest<TCommand> command)
Expand All @@ -33,7 +43,7 @@ public async Task<TResult> RunAsync(CommandRequest<TCommand> command)
var request = new RestRequest(_urlPath)
.AddJsonBody(command);

var response = await _restClient.PostAsync(request);
var response = await _retryPolicy.ExecuteAsync(() => _restClient.PostAsync(request));

if (!response.IsSuccessful)
{
Expand Down
Loading

0 comments on commit 7892c3a

Please sign in to comment.