1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Collections . Immutable ;
3
4
using System . Linq ;
4
5
using System . Net ;
5
6
using System . Net . Http ;
@@ -105,8 +106,8 @@ public async Task UsesCachedResponseIfEtagIsPresentAndGithubReturns304()
105
106
}
106
107
107
108
[ Theory ]
108
- [ MemberData ( nameof ( NonNotModifiedHttpStatusCodesWithSetCacheFailure ) ) ]
109
- public async Task UsesGithubResponseIfEtagIsPresentAndGithubReturnsNon304 ( HttpStatusCode httpStatusCode , bool setCacheThrows )
109
+ [ MemberData ( nameof ( SuccessHttpStatusCodesWithSetCacheFailure ) ) ]
110
+ public async Task UsesGithubResponseIfEtagIsPresentAndGithubReturnsSuccessCode ( HttpStatusCode httpStatusCode , bool setCacheThrows )
110
111
{
111
112
// arrange
112
113
var underlyingClient = Substitute . For < IHttpClient > ( ) ;
@@ -146,20 +147,50 @@ public async Task UsesGithubResponseIfEtagIsPresentAndGithubReturnsNon304(HttpSt
146
147
responseCache . Received ( 1 ) . SetAsync ( request , Arg . Is < CachedResponse . V1 > ( v1 => new ResponseComparer ( ) . Equals ( v1 , CachedResponse . V1 . Create ( githubResponse ) ) ) ) ;
147
148
}
148
149
149
- public static IEnumerable < object [ ] > NonNotModifiedHttpStatusCodesWithSetCacheFailure ( )
150
+ public static IEnumerable < object [ ] > SuccessHttpStatusCodesWithSetCacheFailure ( )
150
151
{
151
- foreach ( var statusCode in Enum . GetValues ( typeof ( HttpStatusCode ) ) )
152
+ var setCacheFails = new [ ] { true , false } ;
153
+
154
+ foreach ( var cacheFail in setCacheFails )
155
+ {
156
+ foreach ( var statusCode in _successStatusCodes . Cast < object > ( ) )
157
+ {
158
+ yield return new [ ] { statusCode , cacheFail } ;
159
+ yield return new [ ] { statusCode , cacheFail } ;
160
+ }
161
+ }
162
+ }
163
+
164
+ private static readonly IImmutableList < HttpStatusCode > _successStatusCodes = Enumerable
165
+ . Range ( 200 , 100 )
166
+ . Where ( code => Enum . IsDefined ( typeof ( HttpStatusCode ) , code ) )
167
+ . Cast < HttpStatusCode > ( )
168
+ . ToImmutableList ( ) ;
169
+
170
+ private static readonly IImmutableList < string > _invalidETags = new [ ]
171
+ {
172
+ null , string . Empty
173
+ } . ToImmutableList ( ) ;
174
+
175
+ public static IEnumerable < object [ ] > SuccessHttpStatusCodesWithSetCacheFailureAndInvalidETags ( )
176
+ {
177
+ var setCacheFails = new [ ] { true , false } ;
178
+ foreach ( var etag in _invalidETags )
152
179
{
153
- if ( statusCode . Equals ( HttpStatusCode . NotModified ) ) continue ;
154
- yield return new [ ] { statusCode , true } ;
155
- yield return new [ ] { statusCode , false } ;
180
+ foreach ( var cacheFail in setCacheFails )
181
+ {
182
+ foreach ( var statusCode in _successStatusCodes . Cast < object > ( ) )
183
+ {
184
+ yield return new [ ] { statusCode , cacheFail , etag } ;
185
+ yield return new [ ] { statusCode , cacheFail , etag } ;
186
+ }
187
+ }
156
188
}
157
189
}
158
190
159
191
[ Theory ]
160
- [ InlineData ( true ) ]
161
- [ InlineData ( false ) ]
162
- public async Task UsesGithubResponseIfCachedEntryIsNull ( bool setCacheThrows )
192
+ [ MemberData ( nameof ( SuccessHttpStatusCodesWithSetCacheFailure ) ) ]
193
+ public async Task UsesGithubResponseIfCachedEntryIsNull ( HttpStatusCode httpStatusCode , bool setCacheThrows )
163
194
{
164
195
// arrange
165
196
var underlyingClient = Substitute . For < IHttpClient > ( ) ;
@@ -171,6 +202,7 @@ public async Task UsesGithubResponseIfCachedEntryIsNull(bool setCacheThrows)
171
202
var cancellationToken = CancellationToken . None ;
172
203
173
204
var githubResponse = Substitute . For < IResponse > ( ) ;
205
+ githubResponse . StatusCode . Returns ( httpStatusCode ) ;
174
206
175
207
underlyingClient . Send ( Arg . Is < IRequest > ( req => req == request && ! req . Headers . Any ( ) ) , cancellationToken ) . ReturnsForAnyArgs ( githubResponse ) ;
176
208
responseCache . GetAsync ( request ) . ReturnsNull ( ) ;
@@ -194,9 +226,8 @@ public async Task UsesGithubResponseIfCachedEntryIsNull(bool setCacheThrows)
194
226
}
195
227
196
228
[ Theory ]
197
- [ InlineData ( true ) ]
198
- [ InlineData ( false ) ]
199
- public async Task UsesGithubResponseIfGetCachedEntryThrows ( bool setCacheThrows )
229
+ [ MemberData ( nameof ( SuccessHttpStatusCodesWithSetCacheFailure ) ) ]
230
+ public async Task UsesGithubResponseIfGetCachedEntryThrows ( HttpStatusCode httpStatusCode , bool setCacheThrows )
200
231
{
201
232
// arrange
202
233
var underlyingClient = Substitute . For < IHttpClient > ( ) ;
@@ -208,6 +239,7 @@ public async Task UsesGithubResponseIfGetCachedEntryThrows(bool setCacheThrows)
208
239
var cancellationToken = CancellationToken . None ;
209
240
210
241
var githubResponse = Substitute . For < IResponse > ( ) ;
242
+ githubResponse . StatusCode . Returns ( httpStatusCode ) ;
211
243
212
244
underlyingClient . Send ( Arg . Is < IRequest > ( req => req == request && ! req . Headers . Any ( ) ) , cancellationToken ) . ReturnsForAnyArgs ( githubResponse ) ;
213
245
responseCache . GetAsync ( Args . Request ) . ThrowsForAnyArgs < Exception > ( ) ;
@@ -231,11 +263,8 @@ public async Task UsesGithubResponseIfGetCachedEntryThrows(bool setCacheThrows)
231
263
}
232
264
233
265
[ Theory ]
234
- [ InlineData ( null , true ) ]
235
- [ InlineData ( null , false ) ]
236
- [ InlineData ( "" , true ) ]
237
- [ InlineData ( "" , false ) ]
238
- public async Task UsesGithubResponseIfCachedEntryEtagIsNullOrEmpty ( string etag , bool setCacheThrows )
266
+ [ MemberData ( nameof ( SuccessHttpStatusCodesWithSetCacheFailureAndInvalidETags ) ) ]
267
+ public async Task UsesGithubResponseIfCachedEntryEtagIsNullOrEmpty ( HttpStatusCode httpStatusCode , bool setCacheThrows , string etag )
239
268
{
240
269
// arrange
241
270
var underlyingClient = Substitute . For < IHttpClient > ( ) ;
@@ -251,6 +280,7 @@ public async Task UsesGithubResponseIfCachedEntryEtagIsNullOrEmpty(string etag,
251
280
var cancellationToken = CancellationToken . None ;
252
281
253
282
var githubResponse = Substitute . For < IResponse > ( ) ;
283
+ githubResponse . StatusCode . Returns ( httpStatusCode ) ;
254
284
255
285
underlyingClient . Send ( Arg . Is < IRequest > ( req => req == request && ! req . Headers . Any ( ) ) , cancellationToken ) . ReturnsForAnyArgs ( githubResponse ) ;
256
286
responseCache . GetAsync ( request ) . Returns ( cachedV1Response ) ;
@@ -272,6 +302,63 @@ public async Task UsesGithubResponseIfCachedEntryEtagIsNullOrEmpty(string etag,
272
302
responseCache . Received ( 1 ) . GetAsync ( request ) ;
273
303
responseCache . Received ( 1 ) . SetAsync ( request , Arg . Is < CachedResponse . V1 > ( v1 => new ResponseComparer ( ) . Equals ( v1 , CachedResponse . V1 . Create ( githubResponse ) ) ) ) ;
274
304
}
305
+
306
+ public static IEnumerable < object [ ] > DoesNotUpdateCacheData ( )
307
+ {
308
+ var codesToExclude = _successStatusCodes
309
+ . Add ( HttpStatusCode . NotModified ) ;
310
+ var failureCodes = Enum
311
+ . GetValues ( typeof ( HttpStatusCode ) )
312
+ . Cast < HttpStatusCode > ( )
313
+ . Except ( codesToExclude )
314
+ . ToList ( ) ;
315
+ var hasCachedResponses = new [ ] { false , true } ;
316
+
317
+ foreach ( var etag in _invalidETags )
318
+ {
319
+ foreach ( var hasCachedResponse in hasCachedResponses )
320
+ {
321
+ foreach ( var statusCode in failureCodes )
322
+ {
323
+ yield return new object [ ]
324
+ {
325
+ statusCode , hasCachedResponse , etag
326
+ } ;
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ [ Theory ]
333
+ [ MemberData ( nameof ( DoesNotUpdateCacheData ) ) ]
334
+ public async Task DoesNotUpdateCacheIfGitHubResponseIsNotSuccessCode ( HttpStatusCode httpStatusCode , bool hasCachedResponse , string etag )
335
+ {
336
+ // arrange
337
+ var underlyingClient = Substitute . For < IHttpClient > ( ) ;
338
+ var responseCache = Substitute . For < IResponseCache > ( ) ;
339
+ var request = Substitute . For < IRequest > ( ) ;
340
+ request . Method . Returns ( HttpMethod . Get ) ;
341
+ request . Headers . Returns ( new Dictionary < string , string > ( ) ) ;
342
+
343
+ var cachedResponse = Substitute . For < IResponse > ( ) ;
344
+ cachedResponse . Headers . Returns ( etag == null ? new Dictionary < string , string > ( ) : new Dictionary < string , string > { { "ETag" , etag } } ) ;
345
+
346
+ var cachedV1Response = CachedResponse . V1 . Create ( cachedResponse ) ;
347
+
348
+ var githubResponse = Substitute . For < IResponse > ( ) ;
349
+ githubResponse . StatusCode . Returns ( httpStatusCode ) ;
350
+
351
+ underlyingClient . Send ( request ) . Returns ( githubResponse ) ;
352
+ responseCache . GetAsync ( request ) . Returns ( hasCachedResponse ? cachedV1Response : null ) ;
353
+
354
+ var cachingHttpClient = new CachingHttpClient ( underlyingClient , responseCache ) ;
355
+
356
+ // act
357
+ _ = await cachingHttpClient . Send ( request , CancellationToken . None ) ;
358
+
359
+ // assert
360
+ responseCache . DidNotReceiveWithAnyArgs ( ) . SetAsync ( Arg . Any < IRequest > ( ) , Arg . Any < CachedResponse . V1 > ( ) ) ;
361
+ }
275
362
}
276
363
277
364
public class TheSetRequestTimeoutMethod
0 commit comments