public async Task ConnectAsync_MakesInactiveCircuitActive() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var registry = CreateRegistry(circuitIdFactory); var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId()); registry.RegisterDisconnectedCircuit(circuitHost); var newClient = Mock.Of <IClientProxy>(); var newConnectionId = "new-id"; // Act var result = await registry.ConnectAsync(circuitHost.CircuitId, newClient, newConnectionId, default); // Assert Assert.Same(circuitHost, result); Assert.Same(newClient, circuitHost.Client.Client); Assert.Same(newConnectionId, circuitHost.Client.ConnectionId); var actual = Assert.Single(registry.ConnectedCircuits.Values); Assert.Same(circuitHost, actual); Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId, out _)); }
public async Task DisconnectedCircuitIsRemovedAfterConfiguredTimeout() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var circuitOptions = new CircuitOptions { DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(3), }; var registry = new TestCircuitRegistry(circuitIdFactory, circuitOptions); var tcs = new TaskCompletionSource <object>(); registry.OnAfterEntryEvicted = () => { tcs.TrySetResult(new object()); }; var circuitHost = TestCircuitHost.Create(); registry.RegisterDisconnectedCircuit(circuitHost); // Act // Verify it's present in the dictionary. Assert.True(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId.Secret, out var _)); await Task.Run(() => tcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10))); Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId.Secret, out var _)); }
public async Task ReconnectBeforeTimeoutDoesNotGetEntryToBeEvicted() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var circuitOptions = new CircuitOptions { DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(8), }; var registry = new TestCircuitRegistry(circuitIdFactory, circuitOptions); var tcs = new TaskCompletionSource <object>(); registry.OnAfterEntryEvicted = () => { tcs.TrySetResult(new object()); }; var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId()); registry.RegisterDisconnectedCircuit(circuitHost); await registry.ConnectAsync(circuitHost.CircuitId, Mock.Of <IClientProxy>(), "new-connection", default); // Act await Task.Run(() => tcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10))); // Verify it's still connected Assert.True(registry.ConnectedCircuits.TryGetValue(circuitHost.CircuitId, out var cacheValue)); Assert.Same(circuitHost, cacheValue); // Nothing should be disconnected. Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId.Secret, out var _)); }
public async Task ConnectAsync_InvokesCircuitHandlers_DisposesCircuitOnFailure() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var registry = CreateRegistry(circuitIdFactory); var handler = new Mock <CircuitHandler> { CallBase = true }; handler.Setup(h => h.OnConnectionUpAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>())).Throws(new InvalidTimeZoneException()); var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId(), handlers: new[] { handler.Object }); registry.Register(circuitHost); var newClient = Mock.Of <IClientProxy>(); var newConnectionId = "new-id"; // Act var result = await registry.ConnectAsync(circuitHost.CircuitId, newClient, newConnectionId, default); // Assert Assert.Null(result); Assert.Null(circuitHost.Handle.CircuitHost); // Will be null if disposed. Assert.Empty(registry.ConnectedCircuits); Assert.Equal(0, registry.DisconnectedCircuits.Count); }
public async Task DisconnectWhenAConnectIsInProgress() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var registry = new TestCircuitRegistry(circuitIdFactory); registry.BeforeConnect = new ManualResetEventSlim(); var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId()); registry.Register(circuitHost); var client = Mock.Of <IClientProxy>(); var oldId = circuitHost.Client.ConnectionId; var newId = "new-connection"; // Act var connect = Task.Run(() => registry.ConnectAsync(circuitHost.CircuitId, client, newId, default)); var disconnect = Task.Run(() => registry.DisconnectAsync(circuitHost, oldId)); registry.BeforeConnect.Set(); await Task.WhenAll(connect, disconnect); // Assert // We expect the disconnect to fail since the client identifier has changed. var actual = Assert.Single(registry.ConnectedCircuits.Values); Assert.Same(circuitHost, actual); Assert.Same(client, circuitHost.Client.Client); Assert.Equal(newId, circuitHost.Client.ConnectionId); Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId.Secret, out _)); }
public async Task DisposeAsync_DisposesScopeAsynchronouslyIfPossible() { // Arrange var serviceScope = new Mock <IServiceScope>(); serviceScope .As <IAsyncDisposable>() .Setup(f => f.DisposeAsync()) .Returns(new ValueTask(Task.CompletedTask)) .Verifiable(); var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object), remoteRenderer: remoteRenderer); // Act await circuitHost.DisposeAsync(); // Assert serviceScope.Verify(s => s.Dispose(), Times.Never()); serviceScope.As <IAsyncDisposable>().Verify(s => s.DisposeAsync(), Times.Once()); Assert.True(remoteRenderer.Disposed); Assert.Null(circuitHost.Handle.CircuitHost); }
public async Task DisposeAsync_DisposesResourcesAndSilencesException() { // Arrange var serviceScope = new Mock <IServiceScope>(); var handler = new Mock <CircuitHandler>(); handler .Setup(h => h.OnCircuitClosedAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>())) .Throws <InvalidTimeZoneException>(); var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object), remoteRenderer: remoteRenderer, handlers: new[] { handler.Object }); var throwOnDisposeComponent = new ThrowOnDisposeComponent(); circuitHost.Renderer.AssignRootComponentId(throwOnDisposeComponent); // Act await circuitHost.DisposeAsync(); // Does not throw // Assert Assert.True(throwOnDisposeComponent.DidCallDispose); serviceScope.Verify(scope => scope.Dispose(), Times.Once()); Assert.True(remoteRenderer.Disposed); }
public async Task DisposeAsync_DisposesRendererWithinSynchronizationContext() { // Arrange var serviceScope = new Mock <IServiceScope>(); var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object), remoteRenderer: remoteRenderer); var component = new DispatcherComponent(circuitHost.Renderer.Dispatcher); circuitHost.Renderer.AssignRootComponentId(component); var original = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(null); // Act & Assert try { Assert.Null(SynchronizationContext.Current); await circuitHost.DisposeAsync(); Assert.True(component.Called); Assert.Null(SynchronizationContext.Current); } finally { // Not sure if the line above messes up the xunit sync context, so just being cautious here. SynchronizationContext.SetSynchronizationContext(original); } }
public async Task DisposeAsync_MarksJSRuntimeAsDisconnectedBeforeDisposingRenderer() { // Arrange var serviceScope = new Mock <IServiceScope>(); var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object), remoteRenderer: remoteRenderer); var component = new PerformJSInteropOnDisposeComponent(circuitHost.JSRuntime); circuitHost.Renderer.AssignRootComponentId(component); var circuitUnhandledExceptions = new List <UnhandledExceptionEventArgs>(); circuitHost.UnhandledException += (sender, eventArgs) => { circuitUnhandledExceptions.Add(eventArgs); }; // Act await circuitHost.DisposeAsync(); // Assert: Component disposal logic sees the exception var componentException = Assert.IsType <JSDisconnectedException>(component.ExceptionDuringDisposeAsync); // Assert: Circuit host notifies about the exception Assert.Collection(circuitUnhandledExceptions, eventArgs => { Assert.Same(componentException, eventArgs.ExceptionObject); }); }
public async Task ConnectAsync_InvokesCircuitHandlers_WhenCircuitWasPreviouslyDisconnected() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var registry = CreateRegistry(circuitIdFactory); var handler = new Mock <CircuitHandler> { CallBase = true }; var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId(), handlers: new[] { handler.Object }); registry.RegisterDisconnectedCircuit(circuitHost); var newClient = Mock.Of <IClientProxy>(); var newConnectionId = "new-id"; // Act var result = await registry.ConnectAsync(circuitHost.CircuitId, newClient, newConnectionId, default); // Assert Assert.NotNull(result); handler.Verify(v => v.OnCircuitOpenedAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); handler.Verify(v => v.OnConnectionUpAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Once()); handler.Verify(v => v.OnConnectionDownAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); handler.Verify(v => v.OnCircuitClosedAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); }
public CircuitHost GetCircuit(IDictionary <object, object> circuitHandles, object circuitKey) { if (circuitSet) { var serviceScope = new Mock <IServiceScope>(); var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object)); return(circuitHost); } return(null); }
// Implement a `CreateCircuitHostAsync` that mocks the construction // of the CircuitHost. public ValueTask <CircuitHost> CreateCircuitHostAsync( IReadOnlyList <ComponentDescriptor> components, CircuitClientProxy client, string baseUri, string uri, ClaimsPrincipal user, IPersistentComponentStateStore store) { var serviceScope = new Mock <IServiceScope>(); var circuitHost = TestCircuitHost.Create(serviceScope: new AsyncServiceScope(serviceScope.Object)); return(ValueTask.FromResult(circuitHost)); }
public void Register_AddsCircuit() { // Arrange var registry = CreateRegistry(); var circuitHost = TestCircuitHost.Create(); // Act registry.Register(circuitHost); // Assert var actual = Assert.Single(registry.ConnectedCircuits.Values); Assert.Same(circuitHost, actual); }
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute) { var serviceCollection = new ServiceCollection(); serviceCollection.AddScoped <IUriHelper>(_ => { var uriHelper = new RemoteUriHelper(NullLogger <RemoteUriHelper> .Instance); uriHelper.InitializeState(uriAbsolute, baseUriAbsolute); return(uriHelper); }); var serviceScope = serviceCollection.BuildServiceProvider().CreateScope(); return(TestCircuitHost.Create(serviceScope)); }
public async Task DisposeAsync_DisposesResources() { // Arrange var serviceScope = new Mock <IServiceScope>(); var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object), remoteRenderer: remoteRenderer); // Act await circuitHost.DisposeAsync(); // Assert serviceScope.Verify(s => s.Dispose(), Times.Once()); Assert.True(remoteRenderer.Disposed); Assert.Null(circuitHost.Handle.CircuitHost); }
public async Task DisconnectAsync_DoesNothing_IfCircuitIsInactive() { // Arrange var registry = CreateRegistry(); var circuitHost = TestCircuitHost.Create(); registry.DisconnectedCircuits.Set(circuitHost.CircuitId.Secret, circuitHost, new MemoryCacheEntryOptions { Size = 1 }); // Act await registry.DisconnectAsync(circuitHost, circuitHost.Client.ConnectionId); // Assert Assert.Empty(registry.ConnectedCircuits.Values); Assert.True(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId.Secret, out _)); }
public async Task DisconnectAsync_DoesNotInvokeCircuitHandlers_WhenCircuitWasNotFound() { // Arrange var registry = CreateRegistry(); var handler = new Mock <CircuitHandler> { CallBase = true }; var circuitHost = TestCircuitHost.Create(handlers: new[] { handler.Object }); // Act await registry.DisconnectAsync(circuitHost, "old-connection"); // Assert handler.Verify(v => v.OnCircuitOpenedAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); handler.Verify(v => v.OnConnectionUpAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); handler.Verify(v => v.OnConnectionDownAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); handler.Verify(v => v.OnCircuitClosedAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>()), Times.Never()); }
public async Task InitializeAsync_RendersRootComponentsInParallel() { // To test that root components are run in parallel, we ensure that each root component // finishes rendering (i.e. returns from SetParametersAsync()) only after all other // root components have started rendering. If the root components were rendered // sequentially, the 1st component would get stuck rendering forever because the // 2nd component had not yet started rendering. We call RenderInParallelComponent.Setup() // to configure how many components will be rendered in advance so that each component // can be assigned a TaskCompletionSource and await the same array of tasks. A timeout // is configured for circuitHost.InitializeAsync() so that the test can fail rather than // hang forever. // Arrange var componentCount = 3; var initializeTimeout = TimeSpan.FromMilliseconds(5000); var cancellationToken = new CancellationToken(); var serviceScope = new Mock <IServiceScope>(); var descriptors = new List <ComponentDescriptor>(); RenderInParallelComponent.Setup(componentCount); for (var i = 0; i < componentCount; i++) { descriptors.Add(new() { ComponentType = typeof(RenderInParallelComponent), Parameters = ParameterView.Empty, Sequence = 0 }); } var circuitHost = TestCircuitHost.Create( serviceScope: new AsyncServiceScope(serviceScope.Object), descriptors: descriptors); // Act object initializeException = null; circuitHost.UnhandledException += (sender, eventArgs) => initializeException = eventArgs.ExceptionObject; var initializeTask = circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of <IDataProtectionProvider>()), cancellationToken); await initializeTask.WaitAsync(initializeTimeout); // Assert: This was not reached only because an exception was thrown in InitializeAsync() Assert.True(initializeException is null, $"An exception was thrown in {nameof(TestCircuitHost.InitializeAsync)}(): {initializeException}"); }
public async Task Connect_WhileDisconnectIsInProgress() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var registry = new TestCircuitRegistry(circuitIdFactory); registry.BeforeDisconnect = new ManualResetEventSlim(); var tcs = new TaskCompletionSource <int>(); var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId()); registry.Register(circuitHost); var client = Mock.Of <IClientProxy>(); var newId = "new-connection"; // Act var disconnect = Task.Run(() => { var task = registry.DisconnectAsync(circuitHost, circuitHost.Client.ConnectionId); tcs.SetResult(0); return(task); }); var connect = Task.Run(async() => { registry.BeforeDisconnect.Set(); await tcs.Task; await registry.ConnectAsync(circuitHost.CircuitId, client, newId, default); }); registry.BeforeDisconnect.Set(); await Task.WhenAll(disconnect, connect); // Assert // We expect the disconnect to finish followed by a reconnect var actual = Assert.Single(registry.ConnectedCircuits.Values); Assert.Same(circuitHost, actual); Assert.Same(client, circuitHost.Client.Client); Assert.Equal(newId, circuitHost.Client.ConnectionId); Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId.Secret, out _)); }
public async Task InitializeAsync_ReportsOwnAsyncExceptions() { // Arrange var handler = new Mock <CircuitHandler>(MockBehavior.Strict); var tcs = new TaskCompletionSource <object>(); var reportedErrors = new List <UnhandledExceptionEventArgs>(); handler .Setup(h => h.OnCircuitOpenedAsync(It.IsAny <Circuit>(), It.IsAny <CancellationToken>())) .Returns(tcs.Task) .Verifiable(); var circuitHost = TestCircuitHost.Create(handlers: new[] { handler.Object }); circuitHost.UnhandledException += (sender, errorInfo) => { Assert.Same(circuitHost, sender); reportedErrors.Add(errorInfo); }; // Act var initializeAsyncTask = circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of <IDataProtectionProvider>()), new CancellationToken()); // Assert: No synchronous exceptions handler.VerifyAll(); Assert.Empty(reportedErrors); // Act: Trigger async exception var ex = new InvalidTimeZoneException(); tcs.SetException(ex); // Assert: The top-level task still succeeds, because the intended usage // pattern is fire-and-forget. await initializeAsyncTask; // Assert: The async exception was reported via the side-channel var aex = Assert.IsType <AggregateException>(reportedErrors.Single().ExceptionObject); Assert.Same(ex, aex.InnerExceptions.Single()); Assert.False(reportedErrors.Single().IsTerminating); }
public async Task DisposeAsync_InvokesCircuitHandler() { // Arrange var cancellationToken = new CancellationToken(); var handler1 = new Mock <CircuitHandler>(MockBehavior.Strict); var handler2 = new Mock <CircuitHandler>(MockBehavior.Strict); var sequence = new MockSequence(); handler1 .InSequence(sequence) .Setup(h => h.OnConnectionDownAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); handler2 .InSequence(sequence) .Setup(h => h.OnConnectionDownAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); handler1 .InSequence(sequence) .Setup(h => h.OnCircuitClosedAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); handler2 .InSequence(sequence) .Setup(h => h.OnCircuitClosedAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); var circuitHost = TestCircuitHost.Create(handlers: new[] { handler1.Object, handler2.Object }); // Act await circuitHost.DisposeAsync(); // Assert handler1.VerifyAll(); handler2.VerifyAll(); }
public async Task InitializeAsync_InvokesHandlers() { // Arrange var cancellationToken = new CancellationToken(); var handler1 = new Mock <CircuitHandler>(MockBehavior.Strict); var handler2 = new Mock <CircuitHandler>(MockBehavior.Strict); var sequence = new MockSequence(); handler1 .InSequence(sequence) .Setup(h => h.OnCircuitOpenedAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); handler2 .InSequence(sequence) .Setup(h => h.OnCircuitOpenedAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); handler1 .InSequence(sequence) .Setup(h => h.OnConnectionUpAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); handler2 .InSequence(sequence) .Setup(h => h.OnConnectionUpAsync(It.IsAny <Circuit>(), cancellationToken)) .Returns(Task.CompletedTask) .Verifiable(); var circuitHost = TestCircuitHost.Create(handlers: new[] { handler1.Object, handler2.Object }); // Act await circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of <IDataProtectionProvider>()), cancellationToken); // Assert handler1.VerifyAll(); handler2.VerifyAll(); }
public async Task GracefullyTerminates_DisconnectedCircuit() { // Arrange var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory(); var circuitId = circuitIdFactory.CreateCircuitId(); var circuitHost = TestCircuitHost.Create(circuitId); var registry = new CircuitRegistry( Options.Create(new CircuitOptions()), NullLogger <CircuitRegistry> .Instance, circuitIdFactory); registry.Register(circuitHost); await registry.DisconnectAsync(circuitHost, "1234"); var middleware = new CircuitDisconnectMiddleware( NullLogger <CircuitDisconnectMiddleware> .Instance, registry, circuitIdFactory, (ctx) => Task.CompletedTask); using var memory = new MemoryStream(); await new FormUrlEncodedContent(new Dictionary <string, string> { ["circuitId"] = circuitId.Secret }).CopyToAsync(memory); memory.Seek(0, SeekOrigin.Begin); var context = new DefaultHttpContext(); context.Request.Method = HttpMethods.Post; context.Request.ContentType = "application/x-www-form-urlencoded"; context.Request.Body = memory; // Act await middleware.Invoke(context); // Assert Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); }
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute) { return(TestCircuitHost.Create(Guid.NewGuid().ToString(), MockServiceScope.Object)); }