Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,20 @@ private async Task<Result<MerchantBalanceState>> LoadHelper(Guid estateId,
using ResolvedDbContext<EstateManagementContext>? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString());
await using EstateManagementContext context = resolvedContext.Context;

MerchantBalanceProjectionState? entity = await context.MerchantBalanceProjectionState.Where(m => m.MerchantId == merchantId).SingleOrDefaultAsync(cancellationToken: cancellationToken);
IQueryable<MerchantBalanceProjectionState> query = context.MerchantBalanceProjectionState.Where(m => m.MerchantId == merchantId);
Result<MerchantBalanceProjectionState> entityResult = await DbQueryHelpers.ExecuteQuerySafeSingleOrDefault(query,
cancellationToken,
$"Error getting merchant balance state for estate {estateId} and merchant {merchantId}");

if (entity == null) {
if (entityResult.Status == ResultStatus.NotFound) {
return Result.Success(new MerchantBalanceState());
}

if (entityResult.IsFailed) {
return Result.Failure(entityResult.Message);
}

MerchantBalanceProjectionState entity = entityResult.Data;

// We have located a state record so we need to translate to the Model type
return Result.Success(new MerchantBalanceState {
Expand Down Expand Up @@ -159,4 +168,4 @@ private async Task<Result<MerchantBalanceState>> LoadHelper(Guid estateId,
private const String ConnectionStringIdentifier = "EstateReportingReadModel";

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Shared.EntityFramework;
using Shouldly;
using System;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using TransactionProcessor.Database.Contexts;
using TransactionProcessor.ProjectionEngine.Repository;
using Xunit;

namespace TransactionProcessor.Tests.RepositoryTests;

public class MerchantBalanceStateRepositoryTests
{
[Fact]
public async Task Load_ReturnsFailure_WhenQueryThrows()
{
Guid estateId = Guid.NewGuid();
Guid merchantId = Guid.NewGuid();

using SqliteConnection connection = new("DataSource=:memory:");
await connection.OpenAsync();

DbContextOptions<EstateManagementContext> setupOptions = new DbContextOptionsBuilder<EstateManagementContext>()
.UseSqlite(connection)
.Options;

await using (EstateManagementContext setupContext = new(setupOptions))
{
await setupContext.Database.EnsureCreatedAsync();
}

DbContextOptions<EstateManagementContext> failingOptions = new DbContextOptionsBuilder<EstateManagementContext>()
.UseSqlite(connection)
.AddInterceptors(new ThrowingDbCommandInterceptor())
.Options;

ServiceProvider serviceProvider = new ServiceCollection()
.AddScoped(_ => new EstateManagementContext(failingOptions))
.BuildServiceProvider();

using IServiceScope scope = serviceProvider.CreateScope();

Mock<IDbContextResolver<EstateManagementContext>> resolver = new();
resolver.Setup(r => r.Resolve(It.IsAny<string>(), It.IsAny<string>()))
.Returns(new ResolvedDbContext<EstateManagementContext>(scope));

MerchantBalanceStateRepository repository = new(resolver.Object);

var result = await repository.Load(estateId, merchantId, CancellationToken.None);

result.IsFailed.ShouldBeTrue();
result.Message.ShouldContain("Error getting merchant balance state");
result.Message.ShouldContain("Simulated transient SQL failure");
}

private sealed class ThrowingDbCommandInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
throw new InvalidOperationException("Simulated transient SQL failure");
}

public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
throw new InvalidOperationException("Simulated transient SQL failure");
}
}
}
6 changes: 4 additions & 2 deletions TransactionProcessor/Bootstrapper/RepositoryRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ public RepositoryRegistry() {
}));
}
else {
this.AddDbContext<EstateManagementContext>(options => options.UseSqlServer(ConfigurationReader.GetConnectionString("TransactionProcessorReadModel"), retry => { }));
this.AddDbContext<EstateManagementContext>(options => options.UseSqlServer(ConfigurationReader.GetConnectionString("TransactionProcessorReadModel"), retry => {
retry.EnableRetryOnFailure();
}));
}
}

Expand Down Expand Up @@ -140,4 +142,4 @@ private void RegisterAggregateCachingFunc() {
});
}
}
}
}
Loading