Skip to content

Commit 395b9d8

Browse files
refactor(tests): address PR feedback - rename helper, fix doc, scoped DbConnection
- Rename CreateClientWithoutRedirectFollowing -> CreateClientWithoutAutoRedirect (clearer name, avoids double-negative) - Fix XML doc comment on the helper (was copy-pasted from GetFollowingGetRedirectsAsync) - Change DbConnection from singleton to scoped with a keep-alive connection, preventing SQLite 'database is locked' errors under concurrent requests - Add Dispose(bool) override to clean up _keepAliveConnection - Update PR description: correct method name and hosted-service ordering description
1 parent 93269b3 commit 395b9d8

3 files changed

Lines changed: 31 additions & 11 deletions

File tree

EssentialCSharp.Web.Tests/ContentRateLimitingTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class ContentRateLimitingTests : IntegrationTestBase
1212
[Test]
1313
public async Task ContentEndpoint_ExceedingPerMinuteLimit_Returns429()
1414
{
15-
using HttpClient client = CreateClientWithoutRedirectFollowing();
15+
using HttpClient client = CreateClientWithoutAutoRedirect();
1616

1717
// Anonymous limit is 10/min. First 10 requests should not be rate-limited.
1818
for (int i = 0; i < 10; i++)

EssentialCSharp.Web.Tests/IntegrationTestBase.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ namespace EssentialCSharp.Web.Tests;
88
public abstract class IntegrationTestBase : WebApplicationTest<WebApplicationFactory, Program>
99
{
1010
/// <summary>
11-
/// Executes a GET request and follows redirect responses while preserving
12-
/// TUnit trace correlation by using <see cref="TracedWebApplicationFactory{T}.CreateClient()"/>.
13-
/// This helper intentionally follows redirects using GET requests only.
11+
/// Creates an <see cref="HttpClient"/> with <c>AllowAutoRedirect = false</c> so callers can
12+
/// assert exact redirect status codes and <c>Location</c> headers without the client
13+
/// silently following them.
1414
/// </summary>
15-
protected HttpClient CreateClientWithoutRedirectFollowing() =>
15+
protected HttpClient CreateClientWithoutAutoRedirect() =>
1616
Factory.Inner.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
1717

1818
protected static async Task<HttpResponseMessage> GetFollowingGetRedirectsAsync(

EssentialCSharp.Web.Tests/WebApplicationFactory.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ namespace EssentialCSharp.Web.Tests;
1414

1515
public sealed class WebApplicationFactory : TestWebApplicationFactory<Program>
1616
{
17-
private static string SqlConnectionString => $"DataSource=file:{Guid.NewGuid()}?mode=memory&cache=shared";
17+
// One GUID per factory instance → each factory gets its own isolated in-memory database.
18+
private readonly string _sqlConnectionString =
19+
$"DataSource=file:{Guid.NewGuid():N}?mode=memory&cache=shared";
20+
21+
// Kept open for the factory lifetime so the shared-cache in-memory database is not dropped
22+
// when per-scope connections are disposed between requests.
23+
private SqliteConnection? _keepAliveConnection;
1824

1925
protected override void ConfigureWebHost(IWebHostBuilder builder)
2026
{
@@ -48,7 +54,19 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
4854
services.Remove(migrationServiceDescriptor);
4955
}
5056

51-
services.AddSingleton<DbConnection>(_ => CreateOpenSqliteConnection());
57+
// Open a keep-alive connection to prevent the shared-cache in-memory database from
58+
// being dropped when per-scope connections are disposed between requests.
59+
_keepAliveConnection = new SqliteConnection(_sqlConnectionString);
60+
_keepAliveConnection.Open();
61+
62+
// Register as scoped so each request scope gets its own SqliteConnection,
63+
// preventing "database is locked" errors under concurrent requests.
64+
services.AddScoped<DbConnection>(_ =>
65+
{
66+
SqliteConnection conn = new(_sqlConnectionString);
67+
conn.Open();
68+
return conn;
69+
});
5270

5371
services.AddDbContext<EssentialCSharpWebContext>((serviceProvider, options) =>
5472
{
@@ -84,11 +102,13 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
84102
});
85103
}
86104

87-
private static SqliteConnection CreateOpenSqliteConnection()
105+
protected override void Dispose(bool disposing)
88106
{
89-
SqliteConnection connection = new(SqlConnectionString);
90-
connection.Open();
91-
return connection;
107+
if (disposing)
108+
{
109+
_keepAliveConnection?.Dispose();
110+
}
111+
base.Dispose(disposing);
92112
}
93113

94114
private sealed class EnsureCreatedHostedService(IServiceProvider serviceProvider) : IHostedService

0 commit comments

Comments
 (0)