@@ -2112,6 +2112,96 @@ public async Task PostAsync_Handles_Secondary_Rate_Limit_With_429_Status()
2112
2112
_mockOctoLogger . Verify ( m => m . LogWarning ( It . Is < string > ( s => s . Contains ( "Secondary rate limit detected" ) ) ) , Times . Once ) ;
2113
2113
}
2114
2114
2115
+ [ Fact ]
2116
+ public async Task GetAsync_Handles_Secondary_Rate_Limit_With_Forbidden_Status ( )
2117
+ {
2118
+ // Arrange
2119
+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
2120
+ handlerMock
2121
+ . Protected ( )
2122
+ . SetupSequence < Task < HttpResponseMessage > > (
2123
+ "SendAsync" ,
2124
+ ItExpr . Is < HttpRequestMessage > ( req => req . Method == HttpMethod . Get ) ,
2125
+ ItExpr . IsAny < CancellationToken > ( ) )
2126
+ . ReturnsAsync ( CreateHttpResponseFactory (
2127
+ statusCode : HttpStatusCode . Forbidden ,
2128
+ content : "You have triggered an abuse detection mechanism" ,
2129
+ headers : new [ ] { ( "Retry-After" , "2" ) } ) ( ) )
2130
+ . ReturnsAsync ( CreateHttpResponseFactory ( content : "SUCCESS_RESPONSE" ) ( ) ) ;
2131
+
2132
+ using var httpClient = new HttpClient ( handlerMock . Object ) ;
2133
+ var githubClient = new GithubClient ( _mockOctoLogger . Object , httpClient , null , _retryPolicy , _dateTimeProvider . Object , PERSONAL_ACCESS_TOKEN ) ;
2134
+
2135
+ // Act
2136
+ var result = await githubClient . GetAsync ( "http://example.com" ) ;
2137
+
2138
+ // Assert
2139
+ result . Should ( ) . Be ( "SUCCESS_RESPONSE" ) ;
2140
+ _mockOctoLogger . Verify ( m => m . LogWarning ( "Secondary rate limit detected (attempt 1/3). Waiting 2 seconds before retrying..." ) , Times . Once ) ;
2141
+ }
2142
+
2143
+ [ Fact ]
2144
+ public async Task SendAsync_Uses_Exponential_Backoff_When_No_Retry_Headers ( )
2145
+ {
2146
+ // Arrange
2147
+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
2148
+ handlerMock
2149
+ . Protected ( )
2150
+ . SetupSequence < Task < HttpResponseMessage > > (
2151
+ "SendAsync" ,
2152
+ ItExpr . Is < HttpRequestMessage > ( req => req . Method == HttpMethod . Patch ) ,
2153
+ ItExpr . IsAny < CancellationToken > ( ) )
2154
+ . ReturnsAsync ( CreateHttpResponseFactory (
2155
+ statusCode : HttpStatusCode . Forbidden ,
2156
+ content : "abuse detection mechanism" ) ( ) )
2157
+ . ReturnsAsync ( CreateHttpResponseFactory (
2158
+ statusCode : HttpStatusCode . Forbidden ,
2159
+ content : "abuse detection mechanism" ) ( ) )
2160
+ . ReturnsAsync ( CreateHttpResponseFactory ( content : "SUCCESS_RESPONSE" ) ( ) ) ;
2161
+
2162
+ using var httpClient = new HttpClient ( handlerMock . Object ) ;
2163
+ var githubClient = new GithubClient ( _mockOctoLogger . Object , httpClient , null , _retryPolicy , _dateTimeProvider . Object , PERSONAL_ACCESS_TOKEN ) ;
2164
+
2165
+ // Act
2166
+ var result = await githubClient . PatchAsync ( "http://example.com" , _rawRequestBody ) ;
2167
+
2168
+ // Assert
2169
+ result . Should ( ) . Be ( "SUCCESS_RESPONSE" ) ;
2170
+ _mockOctoLogger . Verify ( m => m . LogWarning ( "Secondary rate limit detected (attempt 1/3). Waiting 60 seconds before retrying..." ) , Times . Once ) ;
2171
+ _mockOctoLogger . Verify ( m => m . LogWarning ( "Secondary rate limit detected (attempt 2/3). Waiting 120 seconds before retrying..." ) , Times . Once ) ;
2172
+ }
2173
+
2174
+ [ Fact ]
2175
+ public async Task SendAsync_Throws_Exception_After_Max_Secondary_Rate_Limit_Retries ( )
2176
+ {
2177
+ // Arrange
2178
+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
2179
+ handlerMock
2180
+ . Protected ( )
2181
+ . Setup < Task < HttpResponseMessage > > (
2182
+ "SendAsync" ,
2183
+ ItExpr . Is < HttpRequestMessage > ( req => req . Method == HttpMethod . Delete ) ,
2184
+ ItExpr . IsAny < CancellationToken > ( ) )
2185
+ . ReturnsAsync ( CreateHttpResponseFactory (
2186
+ statusCode : HttpStatusCode . TooManyRequests ,
2187
+ content : "Too many requests" ) ( ) ) ;
2188
+
2189
+ using var httpClient = new HttpClient ( handlerMock . Object ) ;
2190
+ var githubClient = new GithubClient ( _mockOctoLogger . Object , httpClient , null , _retryPolicy , _dateTimeProvider . Object , PERSONAL_ACCESS_TOKEN ) ;
2191
+
2192
+ // Act & Assert
2193
+ await FluentActions
2194
+ . Invoking ( async ( ) => await githubClient . DeleteAsync ( "http://example.com" ) )
2195
+ . Should ( )
2196
+ . ThrowExactlyAsync < OctoshiftCliException > ( )
2197
+ . WithMessage ( "Secondary rate limit exceeded. Maximum retries (3) reached. Please wait before retrying your request." ) ;
2198
+
2199
+ // Verify all retry attempts were logged
2200
+ _mockOctoLogger . Verify ( m => m . LogWarning ( "Secondary rate limit detected (attempt 1/3). Waiting 60 seconds before retrying..." ) , Times . Once ) ;
2201
+ _mockOctoLogger . Verify ( m => m . LogWarning ( "Secondary rate limit detected (attempt 2/3). Waiting 120 seconds before retrying..." ) , Times . Once ) ;
2202
+ _mockOctoLogger . Verify ( m => m . LogWarning ( "Secondary rate limit detected (attempt 3/3). Waiting 240 seconds before retrying..." ) , Times . Once ) ;
2203
+ }
2204
+
2115
2205
private object CreateRepositoryMigration ( string migrationId = null , string state = RepositoryMigrationStatus . Succeeded ) => new
2116
2206
{
2117
2207
id = migrationId ?? Guid . NewGuid ( ) . ToString ( ) ,
0 commit comments