Skip to content

Commit b69abed

Browse files
committed
Making D1 tests more robust against CF transient errors
1 parent 21f6256 commit b69abed

File tree

1 file changed

+74
-13
lines changed

1 file changed

+74
-13
lines changed

tests/Cloudflare.NET.Tests/IntegrationTests/D1ApiIntegrationTests.cs

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,39 +72,100 @@ public D1ApiIntegrationTests(CloudflareApiTestFixture fixture, ITestOutputHelper
7272
#region Methods Impl
7373

7474
/// <summary>Asynchronously creates the D1 database required for the tests. This runs once before any tests in this class.</summary>
75+
/// <remarks>
76+
/// The D1 REST API is known to have transient failures (502, 503, 500 errors) that are not caused by our code.
77+
/// Cloudflare has acknowledged these issues but has not resolved them (see GitHub issue #7780).
78+
/// This method implements retry logic to handle these transient failures during fixture setup.
79+
/// </remarks>
7580
public async Task InitializeAsync()
7681
{
7782
// Create a new D1 database for the test run.
78-
var db = await _sut.CreateAsync(_databaseName);
83+
// Retry up to 5 times to handle transient D1 API failures (502, 503, 500 errors).
84+
const int maxRetries = 5;
85+
D1Database? db = null;
86+
87+
for (var attempt = 1; attempt <= maxRetries; attempt++)
88+
{
89+
try
90+
{
91+
db = await _sut.CreateAsync(_databaseName);
92+
break; // Success - exit retry loop.
93+
}
94+
catch (HttpRequestException ex) when (attempt < maxRetries && IsTransientError(ex))
95+
{
96+
_output.WriteLine($"D1 API transient error on attempt {attempt}/{maxRetries}: {ex.Message}. Retrying...");
97+
}
98+
}
99+
100+
if (db is null)
101+
throw new InvalidOperationException($"Failed to create D1 database after {maxRetries} attempts");
102+
79103
_databaseId = db.Uuid;
80104

81105
_output.WriteLine($"Created test database: {_databaseId} ({_databaseName})");
82106

83107
// Create a test table for query operations.
84-
await _sut.QueryAsync(_databaseId, """
85-
CREATE TABLE IF NOT EXISTS test_users (
86-
id INTEGER PRIMARY KEY AUTOINCREMENT,
87-
name TEXT NOT NULL,
88-
email TEXT UNIQUE,
89-
created_at TEXT DEFAULT (datetime('now'))
90-
)
91-
""");
108+
// Also retry this operation for transient failures.
109+
for (var attempt = 1; attempt <= maxRetries; attempt++)
110+
{
111+
try
112+
{
113+
await _sut.QueryAsync(_databaseId, """
114+
CREATE TABLE IF NOT EXISTS test_users (
115+
id INTEGER PRIMARY KEY AUTOINCREMENT,
116+
name TEXT NOT NULL,
117+
email TEXT UNIQUE,
118+
created_at TEXT DEFAULT (datetime('now'))
119+
)
120+
""");
121+
break; // Success - exit retry loop.
122+
}
123+
catch (HttpRequestException ex) when (attempt < maxRetries && IsTransientError(ex))
124+
{
125+
_output.WriteLine($"D1 API transient error creating table on attempt {attempt}/{maxRetries}: {ex.Message}. Retrying...");
126+
}
127+
}
92128

93129
_output.WriteLine("Created test_users table");
94130
}
95131

132+
/// <summary>Determines if the given exception represents a transient D1 API error that should be retried.</summary>
133+
/// <param name="ex">The HTTP request exception to check.</param>
134+
/// <returns><c>true</c> if the error is transient and should be retried; otherwise, <c>false</c>.</returns>
135+
private static bool IsTransientError(HttpRequestException ex)
136+
{
137+
// D1 API is known to return 500, 502, and 503 errors transiently.
138+
// These are Cloudflare infrastructure issues, not problems with our requests.
139+
return ex.StatusCode is HttpStatusCode.InternalServerError
140+
or HttpStatusCode.BadGateway
141+
or HttpStatusCode.ServiceUnavailable
142+
or HttpStatusCode.GatewayTimeout;
143+
}
144+
96145
/// <summary>Asynchronously deletes the D1 database after all tests in this class have run.</summary>
97146
/// <remarks>
98-
/// If cleanup fails, the exception propagates to the test framework. This ensures we are immediately
147+
/// If cleanup fails after retries, the exception propagates to the test framework. This ensures we are immediately
99148
/// aware of any issues with resource cleanup rather than silently logging and continuing.
100149
/// </remarks>
101150
public async Task DisposeAsync()
102151
{
103-
// Clean up the D1 database. Let any exceptions propagate - we want deterministic failure visibility.
152+
// Clean up the D1 database with retry logic for transient D1 API failures.
104153
if (!string.IsNullOrEmpty(_databaseId))
105154
{
106-
await _sut.DeleteAsync(_databaseId);
107-
_output.WriteLine($"Deleted test database: {_databaseId}");
155+
const int maxRetries = 5;
156+
for (var attempt = 1; attempt <= maxRetries; attempt++)
157+
{
158+
try
159+
{
160+
await _sut.DeleteAsync(_databaseId);
161+
_output.WriteLine($"Deleted test database: {_databaseId}");
162+
break; // Success - exit retry loop.
163+
}
164+
catch (HttpRequestException ex) when (attempt < maxRetries && IsTransientError(ex))
165+
{
166+
_output.WriteLine($"D1 API transient error deleting database on attempt {attempt}/{maxRetries}: {ex.Message}. Retrying...");
167+
}
168+
}
108169
}
109170
}
110171

0 commit comments

Comments
 (0)