Skip to content

Commit beec16c

Browse files
committed
Fix uow database connection leak and add tests.
1 parent d32f240 commit beec16c

3 files changed

Lines changed: 120 additions & 1 deletion

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using FluentAssertions;
2+
using Microsoft.Extensions.Logging;
3+
using Moq;
4+
using Npgsql;
5+
using OpenDDD.Domain.Model;
6+
using OpenDDD.Infrastructure.Persistence.OpenDdd.DatabaseSession.Postgres;
7+
using OpenDDD.Infrastructure.Persistence.OpenDdd.UoW.Postgres;
8+
using OpenDDD.Infrastructure.TransactionalOutbox;
9+
using OpenDDD.Tests.Base;
10+
using Xunit.Abstractions;
11+
12+
namespace OpenDDD.Tests.Integration.Infrastructure.Persistence.OpenDdd.UoW
13+
{
14+
[Collection("PostgresTests")]
15+
public class PostgresUoWTests : IntegrationTests, IAsyncLifetime
16+
{
17+
private readonly string _connectionString;
18+
private Mock<IDomainPublisher> _domainPublisherMock = default!;
19+
private Mock<IIntegrationPublisher> _integrationPublisherMock = default!;
20+
private Mock<IOutboxRepository> _outboxRepoMock = default!;
21+
22+
public PostgresUoWTests(ITestOutputHelper output)
23+
: base(output, enableLogging: true)
24+
{
25+
_connectionString = Environment.GetEnvironmentVariable("POSTGRES_TEST_CONNECTION_STRING")
26+
?? "Host=localhost;Port=5432;Database=testdb;Username=testuser;Password=testpassword";
27+
}
28+
29+
public async Task InitializeAsync()
30+
{
31+
_domainPublisherMock = new Mock<IDomainPublisher>();
32+
_domainPublisherMock.Setup(x => x.GetPublishedEvents()).Returns(Array.Empty<IDomainEvent>());
33+
34+
_integrationPublisherMock = new Mock<IIntegrationPublisher>();
35+
_integrationPublisherMock.Setup(x => x.GetPublishedEvents()).Returns(Array.Empty<IIntegrationEvent>());
36+
37+
_outboxRepoMock = new Mock<IOutboxRepository>();
38+
_outboxRepoMock
39+
.Setup(x => x.SaveEventAsync(It.IsAny<IEvent>(), It.IsAny<CancellationToken>()))
40+
.Returns(Task.CompletedTask);
41+
}
42+
43+
public async Task DisposeAsync()
44+
{
45+
46+
}
47+
48+
[Fact]
49+
public async Task UnitOfWork_Should_Not_LeakConnections_When_UsedMultipleTimes()
50+
{
51+
var exceptions = new List<Exception>();
52+
53+
for (int i = 0; i < 300; i++)
54+
{
55+
var uow = await CreateScopedUnitOfWork();
56+
57+
try
58+
{
59+
await uow.StartAsync(default);
60+
await uow.CommitAsync(default);
61+
}
62+
catch (Exception ex)
63+
{
64+
exceptions.Add(ex);
65+
}
66+
finally
67+
{
68+
uow.Dispose();
69+
}
70+
}
71+
72+
exceptions.Should().BeEmpty();
73+
}
74+
75+
private async Task<PostgresOpenDddUnitOfWork> CreateScopedUnitOfWork()
76+
{
77+
var connection = new NpgsqlConnection(_connectionString);
78+
await connection.OpenAsync();
79+
80+
var session = new PostgresDatabaseSession(connection);
81+
82+
var uow = new PostgresOpenDddUnitOfWork(
83+
session,
84+
_domainPublisherMock.Object,
85+
_integrationPublisherMock.Object,
86+
_outboxRepoMock.Object,
87+
LoggerFactory.CreateLogger<PostgresOpenDddUnitOfWork>());
88+
89+
return uow;
90+
}
91+
}
92+
}

src/OpenDDD/Infrastructure/Persistence/OpenDdd/DatabaseSession/Postgres/PostgresDatabaseSession.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace OpenDDD.Infrastructure.Persistence.OpenDdd.DatabaseSession.Postgres
66
{
7-
public class PostgresDatabaseSession : IDatabaseSession
7+
public class PostgresDatabaseSession : IDatabaseSession, IDisposable, IAsyncDisposable
88
{
99
public NpgsqlConnection Connection { get; }
1010
public NpgsqlTransaction? Transaction { get; private set; }
@@ -45,5 +45,31 @@ public async Task RollbackTransactionAsync(CancellationToken ct = default)
4545
Transaction = null;
4646
}
4747
}
48+
49+
public async ValueTask DisposeAsync()
50+
{
51+
if (Transaction != null)
52+
{
53+
await Transaction.DisposeAsync();
54+
Transaction = null;
55+
}
56+
if (Connection != null)
57+
{
58+
await Connection.DisposeAsync();
59+
}
60+
}
61+
62+
public void Dispose()
63+
{
64+
if (Transaction != null)
65+
{
66+
Transaction.Dispose();
67+
Transaction = null;
68+
}
69+
if (Connection != null)
70+
{
71+
Connection.Dispose();
72+
}
73+
}
4874
}
4975
}

src/OpenDDD/Infrastructure/Persistence/OpenDdd/UoW/Postgres/PostgresOpenDddUnitOfWork.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ protected override async Task RollbackTransactionInternalAsync(CancellationToken
4141
protected override void DisposeInternal()
4242
{
4343
_session.RollbackTransactionAsync().GetAwaiter().GetResult();
44+
_session.Dispose();
4445
}
4546
}
4647
}

0 commit comments

Comments
 (0)