14
14
*/
15
15
16
16
using System ;
17
- using System . Reflection ;
17
+ using System . Collections ;
18
+ using System . Collections . Generic ;
19
+ using System . Linq ;
20
+ using System . Net ;
18
21
using FluentAssertions ;
19
22
using MongoDB . Bson ;
20
23
using MongoDB . Bson . TestHelpers ;
21
24
using MongoDB . Bson . TestHelpers . XunitExtensions ;
22
25
using MongoDB . Driver . Core . Clusters ;
26
+ using MongoDB . Driver . Core . Misc ;
27
+ using MongoDB . Driver . Core . Servers ;
28
+ using MongoDB . Driver . Core . TestHelpers . XunitExtensions ;
23
29
using Moq ;
24
30
using Xunit ;
25
31
@@ -218,9 +224,10 @@ public void Dispose_should_have_expected_result(
218
224
Mock . Get ( subject . ServerSession ) . Verify ( m => m . Dispose ( ) , Times . Once ) ;
219
225
}
220
226
221
- [ Fact ]
227
+ [ SkippableFact ]
222
228
public void StartTransaction_should_throw_when_write_concern_is_unacknowledged ( )
223
229
{
230
+ RequireServer . Check ( ) . ClusterType ( ClusterType . ReplicaSet ) . Supports ( Feature . Transactions ) ;
224
231
var cluster = CoreTestConfiguration . Cluster ;
225
232
var session = cluster . StartSession ( ) ;
226
233
var transactionOptions = new TransactionOptions ( writeConcern : WriteConcern . Unacknowledged ) ;
@@ -240,18 +247,234 @@ public void WasUsed_should_call_serverSession()
240
247
241
248
Mock . Get ( subject . ServerSession ) . Verify ( m => m . WasUsed ( ) , Times . Once ) ;
242
249
}
250
+
251
+ [ Theory ]
252
+ [ ParameterAttributeData ]
253
+ public void EnsureTransactionsAreSupported_should_throw_when_there_are_no_connected_servers (
254
+ [ Values ( 0 , 1 , 2 , 3 ) ] int numberOfDisconnectedServers )
255
+ {
256
+ var clusterDescription = CreateClusterDescriptionWithDisconnectedServers ( numberOfDisconnectedServers ) ;
257
+ var subject = CreateSubject ( clusterDescription ) ;
258
+
259
+ var exception = Record . Exception ( ( ) => subject . EnsureTransactionsAreSupported ( ) ) ;
260
+
261
+ var e = exception . Should ( ) . BeOfType < NotSupportedException > ( ) . Subject ;
262
+ e . Message . Should ( ) . Be ( "StartTransaction cannot determine if transactions are supported because there are no connected servers." ) ;
263
+ }
264
+
265
+ // EnsureTransactionsAreSupported scenario codes
266
+ // C = Connected, D = Disconnected
267
+ // P = Primary, S = Secondary, A = Arbiter, R = ShardRouter, U = Unknown
268
+ // T = transactions are supported, N = transactions are not supported
269
+
270
+ [ Theory ]
271
+ [ InlineData ( "DU,CP" ) ]
272
+ [ InlineData ( "CP,DU" ) ]
273
+ [ InlineData ( "DU,CR" ) ]
274
+ [ InlineData ( "CR,DU" ) ]
275
+ public void EnsureTransactionsAreSupported_should_ignore_disconnected_servers ( string scenarios )
276
+ {
277
+ var clusterId = new ClusterId ( 1 ) ;
278
+ var servers =
279
+ SplitScenarios ( scenarios )
280
+ . Select ( ( scenario , i ) =>
281
+ {
282
+ var endPoint = new DnsEndPoint ( "localhost" , 27017 + i ) ;
283
+ var serverId = new ServerId ( clusterId , endPoint ) ;
284
+ var state = MapServerStateCode ( scenario [ 0 ] ) ;
285
+ var type = MapServerTypeCode ( scenario [ 1 ] ) ;
286
+ var version = type == ServerType . ShardRouter ? Feature . ShardedTransactions . FirstSupportedVersion : Feature . Transactions . FirstSupportedVersion ;
287
+ return CreateServerDescription ( serverId , endPoint , state , type , version ) ;
288
+ } )
289
+ . ToList ( ) ;
290
+ var cluster = CreateClusterDescription ( clusterId , servers : servers ) ;
291
+ var subject = CreateSubject ( cluster ) ;
292
+
293
+ subject . EnsureTransactionsAreSupported ( ) ;
294
+ }
295
+
296
+ [ Theory ]
297
+ [ InlineData ( "" ) ]
298
+ [ InlineData ( "DU" ) ]
299
+ [ InlineData ( "CA" ) ]
300
+ [ InlineData ( "DU,DU" ) ]
301
+ [ InlineData ( "DU,CA" ) ]
302
+ [ InlineData ( "CA,DU" ) ]
303
+ [ InlineData ( "CA,CA" ) ]
304
+ public void EnsureTransactionsAreSupported_should_throw_when_there_are_no_connected_data_bearing_servers ( string scenarios )
305
+ {
306
+ var clusterId = new ClusterId ( 1 ) ;
307
+ var servers =
308
+ SplitScenarios ( scenarios )
309
+ . Select ( ( scenario , i ) =>
310
+ {
311
+ var endPoint = new DnsEndPoint ( "localhost" , 27017 + i ) ;
312
+ var serverId = new ServerId ( clusterId , endPoint ) ;
313
+ var state = MapServerStateCode ( scenario [ 0 ] ) ;
314
+ var type = MapServerTypeCode ( scenario [ 1 ] ) ;
315
+ return CreateServerDescription ( serverId , endPoint , state , type ) ;
316
+ } )
317
+ . ToList ( ) ;
318
+ var cluster = CreateClusterDescription ( clusterId , servers : servers ) ;
319
+ var subject = CreateSubject ( cluster ) ;
320
+
321
+ var exception = Record . Exception ( ( ) => subject . EnsureTransactionsAreSupported ( ) ) ;
322
+
323
+ var e = exception . Should ( ) . BeOfType < NotSupportedException > ( ) . Subject ;
324
+ e . Message . Should ( ) . Be ( "StartTransaction cannot determine if transactions are supported because there are no connected servers." ) ;
325
+ }
326
+
327
+ [ Theory ]
328
+ [ InlineData ( "PN" ) ]
329
+ [ InlineData ( "PN,ST" ) ]
330
+ [ InlineData ( "PT,SN" ) ]
331
+ [ InlineData ( "RN" ) ]
332
+ [ InlineData ( "RN,RT" ) ]
333
+ [ InlineData ( "RT,RN" ) ]
334
+ public void EnsureTransactionsAreSupported_should_throw_when_any_connected_data_bearing_server_does_not_support_transactions ( string scenarios )
335
+ {
336
+ var clusterId = new ClusterId ( 1 ) ;
337
+ string unsupportedFeatureName = null ;
338
+ var servers =
339
+ SplitScenarios ( scenarios )
340
+ . Select ( ( scenario , i ) =>
341
+ {
342
+ var endPoint = new DnsEndPoint ( "localhost" , 27017 + i ) ;
343
+ var serverId = new ServerId ( clusterId , endPoint ) ;
344
+ var type = MapServerTypeCode ( scenario [ 0 ] ) ;
345
+ var supportsTransactions = MapSupportsTransactionsCode ( scenario [ 1 ] ) ;
346
+ var feature = type == ServerType . ShardRouter ? Feature . ShardedTransactions : Feature . Transactions ;
347
+ if ( ! supportsTransactions )
348
+ {
349
+ unsupportedFeatureName = feature . Name ;
350
+ }
351
+ var version = supportsTransactions ? feature . FirstSupportedVersion : feature . LastNotSupportedVersion ;
352
+ return CreateServerDescription ( serverId , endPoint , ServerState . Connected , type , version ) ;
353
+ } )
354
+ . ToList ( ) ;
355
+ var cluster = CreateClusterDescription ( clusterId , servers : servers ) ;
356
+ var subject = CreateSubject ( cluster ) ;
357
+
358
+ var exception = Record . Exception ( ( ) => subject . EnsureTransactionsAreSupported ( ) ) ;
359
+
360
+ var e = exception . Should ( ) . BeOfType < NotSupportedException > ( ) . Subject ;
361
+ e . Message . Should ( ) . Contain ( $ "does not support the { unsupportedFeatureName } feature.") ;
362
+ }
243
363
244
364
// private methods
365
+ private ClusterDescription CreateClusterDescription (
366
+ ClusterId clusterId = null ,
367
+ ClusterConnectionMode connectionMode = ClusterConnectionMode . Automatic ,
368
+ ClusterType type = ClusterType . Unknown ,
369
+ IEnumerable < ServerDescription > servers = null )
370
+ {
371
+ clusterId = clusterId ?? new ClusterId ( 1 ) ;
372
+ servers = servers ?? new ServerDescription [ 0 ] ;
373
+ return new ClusterDescription ( clusterId , connectionMode , type , servers ) ;
374
+ }
375
+
376
+ private ClusterDescription CreateClusterDescriptionWithDisconnectedServers ( int numberOfDisconnectedServers )
377
+ {
378
+ var clusterId = new ClusterId ( 1 ) ;
379
+ var servers = Enumerable . Range ( 27017 , numberOfDisconnectedServers ) . Select ( port => CreateDisconnectedServerDescription ( clusterId , port ) ) . ToList ( ) ;
380
+ return CreateClusterDescription ( servers : servers ) ;
381
+ }
382
+
383
+ private ServerDescription CreateDisconnectedServerDescription ( ClusterId clusterId , int port )
384
+ {
385
+ var endPoint = new DnsEndPoint ( "localhost" , port ) ;
386
+ var serverId = new ServerId ( clusterId , endPoint ) ;
387
+ return new ServerDescription ( serverId , endPoint , state : ServerState . Disconnected , type : ServerType . Unknown ) ;
388
+ }
389
+
390
+ private ICluster CreateMockReplicaSetCluster ( )
391
+ {
392
+ var clusterId = new ClusterId ( 1 ) ;
393
+ var endPoint = new DnsEndPoint ( "localhost" , 27017 ) ;
394
+ var serverId = new ServerId ( clusterId , endPoint ) ;
395
+ var version = Feature . Transactions . FirstSupportedVersion ;
396
+ var servers = new [ ] { new ServerDescription ( serverId , endPoint , state : ServerState . Connected , type : ServerType . ReplicaSetPrimary , version : version ) } ;
397
+ var clusterDescription = new ClusterDescription ( clusterId , ClusterConnectionMode . Automatic , ClusterType . ReplicaSet , servers ) ;
398
+ var mockCluster = new Mock < ICluster > ( ) ;
399
+ mockCluster . SetupGet ( m => m . Description ) . Returns ( clusterDescription ) ;
400
+ return mockCluster . Object ;
401
+ }
402
+
403
+ private ServerDescription CreateServerDescription (
404
+ ServerId serverId = null ,
405
+ EndPoint endPoint = null ,
406
+ ServerState state = ServerState . Disconnected ,
407
+ ServerType type = ServerType . Unknown ,
408
+ SemanticVersion version = null )
409
+ {
410
+ endPoint = endPoint ?? new DnsEndPoint ( "localhost" , 27017 ) ;
411
+ serverId = serverId ?? new ServerId ( new ClusterId ( 1 ) , endPoint ) ;
412
+ version = version ?? SemanticVersion . Parse ( "4.0.0" ) ;
413
+ return new ServerDescription ( serverId , endPoint , state : state , type : type , version : version ) ;
414
+ }
415
+
245
416
private CoreSession CreateSubject (
246
417
ICluster cluster = null ,
247
418
ICoreServerSession serverSession = null ,
248
419
CoreSessionOptions options = null )
249
420
{
250
- cluster = cluster ?? Mock . Of < ICluster > ( ) ;
421
+ cluster = cluster ?? CreateMockReplicaSetCluster ( ) ;
251
422
serverSession = serverSession ?? Mock . Of < ICoreServerSession > ( ) ;
252
423
options = options ?? new CoreSessionOptions ( ) ;
253
424
return new CoreSession ( cluster , serverSession , options ) ;
254
425
}
426
+
427
+ private CoreSession CreateSubject ( ClusterDescription clusterDescription )
428
+ {
429
+ var mockCluster = new Mock < ICluster > ( ) ;
430
+ mockCluster . SetupGet ( m => m . Description ) . Returns ( clusterDescription ) ;
431
+ return CreateSubject ( cluster : mockCluster . Object ) ;
432
+ }
433
+
434
+ private ServerState MapServerStateCode ( char code )
435
+ {
436
+ switch ( code )
437
+ {
438
+ case 'C' : return ServerState . Connected ;
439
+ case 'D' : return ServerState . Disconnected ;
440
+ default : throw new ArgumentException ( $ "Invalid ServerState code: \" { code } \" .", nameof ( code ) ) ;
441
+ }
442
+ }
443
+
444
+ private ServerType MapServerTypeCode ( char code )
445
+ {
446
+ switch ( code )
447
+ {
448
+ case 'A' : return ServerType . ReplicaSetArbiter ;
449
+ case 'P' : return ServerType . ReplicaSetPrimary ;
450
+ case 'R' : return ServerType . ShardRouter ;
451
+ case 'S' : return ServerType . ReplicaSetSecondary ;
452
+ case 'U' : return ServerType . Unknown ;
453
+ default : throw new ArgumentException ( $ "Invalid ServerType code: \" { code } \" .", nameof ( code ) ) ;
454
+ }
455
+ }
456
+
457
+ private bool MapSupportsTransactionsCode ( char code )
458
+ {
459
+ switch ( code )
460
+ {
461
+ case 'N' : return false ;
462
+ case 'T' : return true ;
463
+ default : throw new ArgumentException ( $ "Invalid SupportsTransactions code: \" { code } \" .", nameof ( code ) ) ;
464
+ }
465
+ }
466
+
467
+ private IEnumerable < string > SplitScenarios ( string scenarios )
468
+ {
469
+ if ( scenarios == "" )
470
+ {
471
+ return Enumerable . Empty < string > ( ) ;
472
+ }
473
+ else
474
+ {
475
+ return scenarios . Split ( ',' ) ;
476
+ }
477
+ }
255
478
}
256
479
257
480
public static class CoreSessionReflector
@@ -262,5 +485,7 @@ public static void _isCommitTransactionInProgress(this CoreSession obj, bool val
262
485
{
263
486
Reflector . SetFieldValue ( obj , nameof ( _isCommitTransactionInProgress ) , value ) ;
264
487
}
488
+
489
+ public static void EnsureTransactionsAreSupported ( this CoreSession obj ) => Reflector . Invoke ( obj , nameof ( EnsureTransactionsAreSupported ) ) ;
265
490
}
266
491
}
0 commit comments