@@ -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