@@ -368,6 +368,156 @@ bool ExpectedErrors(WriteContext writeContext)
368
368
}
369
369
}
370
370
371
+ [ Fact ]
372
+ public async Task CanBeInducedByCloseMessageWithAllowReconnectSet ( )
373
+ {
374
+ bool ExpectedErrors ( WriteContext writeContext )
375
+ {
376
+ return writeContext . LoggerName == typeof ( HubConnection ) . FullName &&
377
+ ( writeContext . EventId . Name == "ReceivedCloseWithError" ||
378
+ writeContext . EventId . Name == "ReconnectingWithError" ) ;
379
+ }
380
+
381
+ var failReconnectTcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
382
+
383
+ using ( StartVerifiableLog ( ExpectedErrors ) )
384
+ {
385
+ var builder = new HubConnectionBuilder ( ) . WithLoggerFactory ( LoggerFactory ) . WithUrl ( "http://example.com" ) ;
386
+ var testConnectionFactory = default ( ReconnectingConnectionFactory ) ;
387
+
388
+ testConnectionFactory = new ReconnectingConnectionFactory ( ( ) => new TestConnection ( ) ) ;
389
+ builder . Services . AddSingleton < IConnectionFactory > ( testConnectionFactory ) ;
390
+
391
+ var retryContexts = new List < RetryContext > ( ) ;
392
+ var mockReconnectPolicy = new Mock < IRetryPolicy > ( ) ;
393
+ mockReconnectPolicy . Setup ( p => p . NextRetryDelay ( It . IsAny < RetryContext > ( ) ) ) . Returns < RetryContext > ( context =>
394
+ {
395
+ retryContexts . Add ( context ) ;
396
+ return TimeSpan . Zero ;
397
+ } ) ;
398
+ builder . WithAutomaticReconnect ( mockReconnectPolicy . Object ) ;
399
+
400
+ await using var hubConnection = builder . Build ( ) ;
401
+ var reconnectingCount = 0 ;
402
+ var reconnectedCount = 0 ;
403
+ var reconnectingErrorTcs = new TaskCompletionSource < Exception > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
404
+ var reconnectedConnectionIdTcs = new TaskCompletionSource < string > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
405
+ var closedErrorTcs = new TaskCompletionSource < Exception > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
406
+
407
+ hubConnection . Reconnecting += error =>
408
+ {
409
+ reconnectingCount ++ ;
410
+ reconnectingErrorTcs . SetResult ( error ) ;
411
+ return Task . CompletedTask ;
412
+ } ;
413
+
414
+ hubConnection . Reconnected += connectionId =>
415
+ {
416
+ reconnectedCount ++ ;
417
+ reconnectedConnectionIdTcs . SetResult ( connectionId ) ;
418
+ return Task . CompletedTask ;
419
+ } ;
420
+
421
+ hubConnection . Closed += error =>
422
+ {
423
+ closedErrorTcs . SetResult ( error ) ;
424
+ return Task . CompletedTask ;
425
+ } ;
426
+
427
+ await hubConnection . StartAsync ( ) . OrTimeout ( ) ;
428
+
429
+ var currentConnection = await testConnectionFactory . GetNextOrCurrentTestConnection ( ) ;
430
+ await currentConnection . ReceiveJsonMessage ( new
431
+ {
432
+ type = HubProtocolConstants . CloseMessageType ,
433
+ error = "Error!" ,
434
+ allowReconnect = true ,
435
+ } ) ;
436
+
437
+ var reconnectingException = await reconnectingErrorTcs . Task . OrTimeout ( ) ;
438
+ var expectedMessage = "The server closed the connection with the following error: Error!" ;
439
+
440
+ Assert . Equal ( expectedMessage , reconnectingException . Message ) ;
441
+ Assert . Single ( retryContexts ) ;
442
+ Assert . Equal ( expectedMessage , retryContexts [ 0 ] . RetryReason . Message ) ;
443
+ Assert . Equal ( 0 , retryContexts [ 0 ] . PreviousRetryCount ) ;
444
+ Assert . Equal ( TimeSpan . Zero , retryContexts [ 0 ] . ElapsedTime ) ;
445
+
446
+ await reconnectedConnectionIdTcs . Task . OrTimeout ( ) ;
447
+
448
+ await hubConnection . StopAsync ( ) . OrTimeout ( ) ;
449
+
450
+ var closeError = await closedErrorTcs . Task . OrTimeout ( ) ;
451
+ Assert . Null ( closeError ) ;
452
+ Assert . Equal ( 1 , reconnectingCount ) ;
453
+ Assert . Equal ( 1 , reconnectedCount ) ;
454
+ }
455
+ }
456
+
457
+ [ Fact ]
458
+ public async Task CannotBeInducedByCloseMessageWithAllowReconnectOmitted ( )
459
+ {
460
+ bool ExpectedErrors ( WriteContext writeContext )
461
+ {
462
+ return writeContext . LoggerName == typeof ( HubConnection ) . FullName &&
463
+ ( writeContext . EventId . Name == "ReceivedCloseWithError" ||
464
+ writeContext . EventId . Name == "ShutdownWithError" ) ;
465
+ }
466
+
467
+ var failReconnectTcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
468
+
469
+ using ( StartVerifiableLog ( ExpectedErrors ) )
470
+ {
471
+ var builder = new HubConnectionBuilder ( ) . WithLoggerFactory ( LoggerFactory ) . WithUrl ( "http://example.com" ) ;
472
+ var testConnectionFactory = default ( ReconnectingConnectionFactory ) ;
473
+
474
+ testConnectionFactory = new ReconnectingConnectionFactory ( ( ) => new TestConnection ( ) ) ;
475
+ builder . Services . AddSingleton < IConnectionFactory > ( testConnectionFactory ) ;
476
+
477
+ var reconnectingCount = 0 ;
478
+ var nextRetryDelayCallCount = 0 ;
479
+ var closedErrorTcs = new TaskCompletionSource < Exception > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
480
+
481
+ var mockReconnectPolicy = new Mock < IRetryPolicy > ( ) ;
482
+ mockReconnectPolicy . Setup ( p => p . NextRetryDelay ( It . IsAny < RetryContext > ( ) ) ) . Returns < RetryContext > ( context =>
483
+ {
484
+ nextRetryDelayCallCount ++ ;
485
+ return TimeSpan . Zero ;
486
+ } ) ;
487
+
488
+ builder . WithAutomaticReconnect ( mockReconnectPolicy . Object ) ;
489
+
490
+ await using var hubConnection = builder . Build ( ) ;
491
+
492
+ hubConnection . Reconnecting += error =>
493
+ {
494
+ reconnectingCount ++ ;
495
+ return Task . CompletedTask ;
496
+ } ;
497
+
498
+ hubConnection . Closed += error =>
499
+ {
500
+ closedErrorTcs . SetResult ( error ) ;
501
+ return Task . CompletedTask ;
502
+ } ;
503
+
504
+ await hubConnection . StartAsync ( ) . OrTimeout ( ) ;
505
+
506
+ var currentConnection = await testConnectionFactory . GetNextOrCurrentTestConnection ( ) ;
507
+ await currentConnection . ReceiveJsonMessage ( new
508
+ {
509
+ type = HubProtocolConstants . CloseMessageType ,
510
+ error = "Error!" ,
511
+ } ) ;
512
+
513
+ var closeError = await closedErrorTcs . Task . OrTimeout ( ) ;
514
+
515
+ Assert . Equal ( "The server closed the connection with the following error: Error!" , closeError . Message ) ;
516
+ Assert . Equal ( 0 , nextRetryDelayCallCount ) ;
517
+ Assert . Equal ( 0 , reconnectingCount ) ;
518
+ }
519
+ }
520
+
371
521
[ Fact ]
372
522
public async Task EventsNotFiredIfFirstRetryDelayIsNull ( )
373
523
{
0 commit comments