Adding tests for SSE transport #413

This commit is contained in:
Pawel Kadluczka 2017-07-10 17:29:56 -07:00
parent bcefbae00c
commit 2854e868ec
16 changed files with 531 additions and 227 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26606.0
VisualStudioVersion = 15.0.26706.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}"
EndProject
@ -53,6 +53,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{6CEC3DC2-5B01-45A8-8F0D-8531315DA90B}"
ProjectSection(SolutionItems) = preProject
test\Common\ChannelExtensions.cs = test\Common\ChannelExtensions.cs
test\Common\ServerFixture.cs = test\Common\ServerFixture.cs
test\Common\TaskExtensions.cs = test\Common\TaskExtensions.cs
EndProjectSection
EndProject
@ -203,4 +204,7 @@ Global
{B0243F99-2D3F-4CC6-AD71-E3F891B64724} = {DA69F624-5398-4884-87E4-B816698CDE65}
{E081EE41-D95F-4AD2-BC0B-4B562C0A2A47} = {DA69F624-5398-4884-87E4-B816698CDE65}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
EndGlobalSection
EndGlobal

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.SignalR
// Nothing should be writing to the HubConnectionContext
output.Out.TryComplete();
// This should unwind once we complete the output
// This should unwind once we complete the output
await writingOutputTask;
}
}

View File

@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
case ServerSentEventsMessageParser.ParseResult.Incomplete:
if (result.IsCompleted)
{
throw new FormatException("Incomplete message");
throw new FormatException("Incomplete message.");
}
break;
}

View File

@ -2,19 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging.Testing;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.SignalR.Tests
namespace Microsoft.AspNetCore.SignalR.Tests.Common
{
public class ServerFixture : IDisposable
public class ServerFixture<TStartup> : IDisposable
where TStartup : class
{
private ILoggerFactory _loggerFactory;
private ILogger _logger;
@ -28,37 +27,21 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public ServerFixture()
{
var testLog = AssemblyTestLog.ForAssembly(typeof(ServerFixture).Assembly);
_logToken = testLog.StartTestLog(null, typeof(ServerFixture).FullName, out _loggerFactory, "ServerFixture");
_logger = _loggerFactory.CreateLogger<ServerFixture>();
var testLog = AssemblyTestLog.ForAssembly(typeof(ServerFixture<TStartup>).Assembly);
_logToken = testLog.StartTestLog(null, typeof(ServerFixture<TStartup>).FullName, out _loggerFactory, "ServerFixture");
_logger = _loggerFactory.CreateLogger<ServerFixture<TStartup>>();
StartServer();
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSockets();
services.AddSignalR();
services.AddEndPoint<EchoEndPoint>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSockets(options => options.MapEndPoint<EchoEndPoint>("echo"));
app.UseSignalR(options => options.MapHub<UncreatableHub>("uncreatable"));
}
}
private void StartServer()
{
host = new WebHostBuilder()
.ConfigureLogging(builder => builder.AddProvider(new ForwardingLoggerProvider(_loggerFactory)))
.UseStartup(typeof(TStartup))
.UseKestrel()
.UseUrls(BaseUrl)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
var t = Task.Run(() => host.Start());
@ -86,6 +69,26 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{
_logger.LogInformation("Shutting down test server");
host.Dispose();
_loggerFactory.Dispose();
}
private class ForwardingLoggerProvider : ILoggerProvider
{
private readonly ILoggerFactory _loggerFactory;
public ForwardingLoggerProvider(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public void Dispose()
{
}
public ILogger CreateLogger(string categoryName)
{
return _loggerFactory.CreateLogger(categoryName);
}
}
}
}

View File

@ -3,240 +3,264 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Channels;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Testing;
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{
public class HubConnectionTests : IDisposable
[CollectionDefinition(Name)]
public class HubConnectionTestsCollection : ICollectionFixture<ServerFixture<Startup>>
{
private readonly TestServer _testServer;
private static readonly bool _verbose = string.Equals(Environment.GetEnvironmentVariable("SIGNALR_TEST_VERBOSE"), "1");
public const string Name = "EndToEndTests";
}
public HubConnectionTests()
[Collection(HubConnectionTestsCollection.Name)]
public class HubConnectionTests : LoggedTest
{
private readonly ServerFixture<Startup> _serverFixture;
public HubConnectionTests(ServerFixture<Startup> serverFixture, ITestOutputHelper output)
: base(output)
{
var webHostBuilder = new WebHostBuilder().
ConfigureServices(services =>
if (serverFixture == null)
{
throw new ArgumentNullException(nameof(serverFixture));
}
_serverFixture = serverFixture;
}
[Theory]
[MemberData(nameof(HubProtocolsXTransports))]
public async Task CheckFixedMessage(IHubProtocol protocol, TransportType transport)
{
using (StartLog(out var loggerFactory))
{
var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
{
services.AddSignalR();
})
.ConfigureLogging(loggerFactory =>
await connection.StartAsync().OrTimeout();
var result = await connection.InvokeAsync<string>(nameof(TestHub.HelloWorld)).OrTimeout();
Assert.Equal("Hello World!", result);
}
catch (Exception ex)
{
if (_verbose)
{
loggerFactory.AddConsole();
}
})
.Configure(app =>
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
app.UseSignalR(routes =>
{
routes.MapHub<TestHub>("hubs");
});
});
_testServer = new TestServer(webHostBuilder);
}
[Theory]
[MemberData(nameof(HubProtocols))]
public async Task CheckFixedMessage(IHubProtocol protocol)
{
var loggerFactory = CreateLogger();
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
{
await connection.StartAsync();
var result = await connection.InvokeAsync<string>(nameof(TestHub.HelloWorld));
Assert.Equal("Hello World!", result);
}
finally
{
await connection.DisposeAsync();
await connection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(HubProtocols))]
public async Task CanSendAndReceiveMessage(IHubProtocol protocol)
[MemberData(nameof(HubProtocolsXTransports))]
public async Task CanSendAndReceiveMessage(IHubProtocol protocol, TransportType transport)
{
var loggerFactory = CreateLogger();
const string originalMessage = "SignalR";
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
using (StartLog(out var loggerFactory))
{
await connection.StartAsync();
const string originalMessage = "SignalR";
var result = await connection.InvokeAsync<string>(nameof(TestHub.Echo), originalMessage);
var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
{
await connection.StartAsync().OrTimeout();
Assert.Equal(originalMessage, result);
}
finally
{
await connection.DisposeAsync();
var result = await connection.InvokeAsync<string>(nameof(TestHub.Echo), originalMessage).OrTimeout();
Assert.Equal(originalMessage, result);
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(HubProtocols))]
public async Task MethodsAreCaseInsensitive(IHubProtocol protocol)
[MemberData(nameof(HubProtocolsXTransports))]
public async Task MethodsAreCaseInsensitive(IHubProtocol protocol, TransportType transport)
{
var loggerFactory = CreateLogger();
const string originalMessage = "SignalR";
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
using (StartLog(out var loggerFactory))
{
await connection.StartAsync();
const string originalMessage = "SignalR";
var result = await connection.InvokeAsync<string>(nameof(TestHub.Echo).ToLowerInvariant(), originalMessage);
var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
{
await connection.StartAsync().OrTimeout();
Assert.Equal(originalMessage, result);
}
finally
{
await connection.DisposeAsync();
var result = await connection.InvokeAsync<string>(nameof(TestHub.Echo).ToLowerInvariant(), originalMessage).OrTimeout();
Assert.Equal(originalMessage, result);
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(HubProtocols))]
public async Task CanInvokeClientMethodFromServer(IHubProtocol protocol)
[MemberData(nameof(HubProtocolsXTransports))]
public async Task CanInvokeClientMethodFromServer(IHubProtocol protocol, TransportType transport)
{
var loggerFactory = CreateLogger();
const string originalMessage = "SignalR";
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
using (StartLog(out var loggerFactory))
{
await connection.StartAsync();
const string originalMessage = "SignalR";
var tcs = new TaskCompletionSource<string>();
connection.On<string>("Echo", tcs.SetResult);
var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
{
await connection.StartAsync().OrTimeout();
await connection.InvokeAsync(nameof(TestHub.CallEcho), originalMessage).OrTimeout();
var tcs = new TaskCompletionSource<string>();
connection.On<string>("Echo", tcs.SetResult);
Assert.Equal(originalMessage, await tcs.Task.OrTimeout());
}
finally
{
await connection.DisposeAsync().OrTimeout();
await connection.InvokeAsync(nameof(TestHub.CallEcho), originalMessage).OrTimeout();
Assert.Equal(originalMessage, await tcs.Task.OrTimeout());
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(HubProtocols))]
public async Task CanStreamClientMethodFromServer(IHubProtocol protocol)
[MemberData(nameof(HubProtocolsXTransports))]
public async Task CanStreamClientMethodFromServer(IHubProtocol protocol, TransportType transport)
{
var loggerFactory = CreateLogger();
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
using (StartLog(out var loggerFactory))
{
await connection.StartAsync();
var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
var connection = new HubConnection(httpConnection, protocol, loggerFactory);
try
{
await connection.StartAsync().OrTimeout();
var tcs = new TaskCompletionSource<string>();
var tcs = new TaskCompletionSource<string>();
var results = await connection.Stream<string>(nameof(TestHub.Stream)).ReadAllAsync().OrTimeout();
var results = await connection.Stream<string>(nameof(TestHub.Stream)).ReadAllAsync().OrTimeout();
Assert.Equal(new[] { "a", "b", "c" }, results.ToArray());
}
finally
{
await connection.DisposeAsync().OrTimeout();
Assert.Equal(new[] { "a", "b", "c" }, results.ToArray());
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(HubProtocols))]
public async Task ServerClosesConnectionIfHubMethodCannotBeResolved(IHubProtocol hubProtocol)
[MemberData(nameof(HubProtocolsXTransports))]
public async Task ServerClosesConnectionIfHubMethodCannotBeResolved(IHubProtocol hubProtocol, TransportType transport)
{
var loggerFactory = CreateLogger();
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, hubProtocol, loggerFactory);
try
using (StartLog(out var loggerFactory))
{
await connection.StartAsync();
var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
var connection = new HubConnection(httpConnection, hubProtocol, loggerFactory);
try
{
await connection.StartAsync().OrTimeout();
var ex = await Assert.ThrowsAnyAsync<Exception>(
async () => await connection.InvokeAsync("!@#$%"));
var ex = await Assert.ThrowsAnyAsync<Exception>(
async () => await connection.InvokeAsync("!@#$%")).OrTimeout();
Assert.Equal("Unknown hub method '!@#$%'", ex.Message);
}
finally
{
await connection.DisposeAsync();
Assert.Equal("Unknown hub method '!@#$%'", ex.Message);
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
}
}
public static IEnumerable<object[]> HubProtocols() =>
new[]
public static IEnumerable<object []> HubProtocolsXTransports()
{
foreach (var protocol in HubProtocols)
{
new object[] { new JsonHubProtocol(new JsonSerializer()) },
new object[] { new MessagePackHubProtocol() },
foreach (var transport in TransportTypes())
{
yield return new object[] { protocol, transport };
}
}
}
public static IEnumerable<IHubProtocol> HubProtocols =>
new IHubProtocol[]
{
new JsonHubProtocol(new JsonSerializer()),
new MessagePackHubProtocol(),
};
public void Dispose()
public static IEnumerable<TransportType> TransportTypes()
{
_testServer.Dispose();
}
private static ILoggerFactory CreateLogger()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
if (WebsocketsSupported())
{
builder.AddConsole();
builder.AddFilter<ConsoleLoggerProvider>(level => level >= (_verbose ? LogLevel.Trace : LogLevel.Error));
builder.AddDebug();
});
return serviceCollection.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
}
public class TestHub : Hub
{
public string HelloWorld()
{
return "Hello World!";
// TODO: Currently we are always sending Text messages over websockets which does not work
// with binary protocols. It is getting fixed separately.
// The tests are also failing on full framework when using WebSockets transport
// due to: https://github.com/aspnet/SignalR/issues/568
// yield return TransportType.WebSockets;
}
yield return TransportType.ServerSentEvents;
yield return TransportType.LongPolling;
public string Echo(string message)
bool WebsocketsSupported()
{
return message;
}
try
{
new System.Net.WebSockets.ClientWebSocket();
}
catch (PlatformNotSupportedException)
{
return false;
}
public async Task CallEcho(string message)
{
await Clients.Client(Context.ConnectionId).InvokeAsync("Echo", message);
}
public IObservable<string> Stream()
{
return new[] { "a", "b", "c" }.ToObservable();
return true;
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{
public class TestHub : Hub
{
public string HelloWorld()
{
return "Hello World!";
}
public string Echo(string message)
{
return message;
}
public async Task CallEcho(string message)
{
await Clients.Client(Context.ConnectionId).InvokeAsync("Echo", message);
}
public IObservable<string> Stream()
{
return new[] { "a", "b", "c" }.ToObservable();
}
}
}

View File

@ -4,10 +4,13 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp2.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp2.0</TargetFrameworks>
<RuntimeIdentifier Condition="'$(TargetFramework)' != 'netcoreapp2.0'">win7-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\ServerFixture.cs" Link="ServerFixture.cs" />
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
<Compile Include="..\Common\ChannelExtensions.cs" Link="ChannelExtensions.cs" />
</ItemGroup>
@ -22,8 +25,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
}
public void Configure(IApplicationBuilder app)
{
app.UseSignalR(routes =>
{
routes.MapHub<TestHub>("hubs");
});
}
}
}

View File

@ -12,7 +12,6 @@ using System.Threading.Tasks.Channels;
using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Internal;
using Microsoft.Extensions.Logging;
using Moq;
using Moq.Protected;
using Xunit;

View File

@ -4,9 +4,12 @@
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Channels;
using Microsoft.AspNetCore.Client.Tests;
using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Internal;
@ -61,5 +64,219 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
copyToAsyncTcs.SetResult(0);
}
}
[Fact]
public async Task SSETransportStopsSendAndReceiveLoopsWhenTransportStopped()
{
var eventStreamCts = new CancellationTokenSource();
var mockHttpHandler = new Mock<HttpMessageHandler>();
mockHttpHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
{
await Task.Yield();
var mockStream = new Mock<Stream>();
mockStream
.Setup(s => s.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Returns<Stream, int, CancellationToken>(async (stream, bufferSize, t) =>
{
var buffer = Encoding.ASCII.GetBytes("data: 3:abc\r\n\r\n");
while (!eventStreamCts.IsCancellationRequested)
{
await stream.WriteAsync(buffer, 0, buffer.Length);
}
});
return new HttpResponseMessage { Content = new StreamContent(mockStream.Object) };
});
Task transportActiveTask;
using (var httpClient = new HttpClient(mockHttpHandler.Object))
{
var sseTransport = new ServerSentEventsTransport(httpClient);
try
{
var connectionToTransport = Channel.CreateUnbounded<SendMessage>();
var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
await sseTransport.StartAsync(
new Uri("http://fakeuri.org"), channelConnection, connectionId: string.Empty).OrTimeout();
transportActiveTask = sseTransport.Running;
Assert.False(transportActiveTask.IsCompleted);
var message = await transportToConnection.In.ReadAsync().AsTask().OrTimeout();
Assert.Equal("3:abc", Encoding.ASCII.GetString(message));
}
finally
{
await sseTransport.StopAsync().OrTimeout();
}
await transportActiveTask.OrTimeout();
eventStreamCts.Cancel();
}
}
[Fact]
public async Task SSETransportStopsWithErrorIfServerSendsIncompleteResults()
{
var mockHttpHandler = new Mock<HttpMessageHandler>();
mockHttpHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
{
await Task.Yield();
var mockStream = new Mock<Stream>();
mockStream
.Setup(s => s.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Returns<Stream, int, CancellationToken>(async (stream, bufferSize, t) =>
{
var buffer = Encoding.ASCII.GetBytes("data: 3:a");
await stream.WriteAsync(buffer, 0, buffer.Length);
});
return new HttpResponseMessage { Content = new StreamContent(mockStream.Object) };
});
using (var httpClient = new HttpClient(mockHttpHandler.Object))
{
var sseTransport = new ServerSentEventsTransport(httpClient);
var connectionToTransport = Channel.CreateUnbounded<SendMessage>();
var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
await sseTransport.StartAsync(
new Uri("http://fakeuri.org"), channelConnection, connectionId: string.Empty).OrTimeout();
var exception = await Assert.ThrowsAsync<FormatException>(() => sseTransport.Running.OrTimeout());
Assert.Equal("Incomplete message.", exception.Message);
}
}
[Fact]
public async Task SSETransportStopsWithErrorIfSendingMessageFails()
{
var eventStreamTcs = new TaskCompletionSource<object>();
var copyToAsyncTcs = new TaskCompletionSource<int>();
var mockHttpHandler = new Mock<HttpMessageHandler>();
mockHttpHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
{
await Task.Yield();
if (request.Headers.Accept?.Contains(new MediaTypeWithQualityHeaderValue("text/event-stream")) == true)
{
// Receive loop started - allow stopping the transport
eventStreamTcs.SetResult(null);
// returns unfinished task to block pipelines
var mockStream = new Mock<Stream>();
mockStream
.Setup(s => s.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Returns(copyToAsyncTcs.Task);
return new HttpResponseMessage { Content = new StreamContent(mockStream.Object) };
}
return ResponseUtils.CreateResponse(System.Net.HttpStatusCode.InternalServerError);
});
using (var httpClient = new HttpClient(mockHttpHandler.Object))
{
var sseTransport = new ServerSentEventsTransport(httpClient);
var connectionToTransport = Channel.CreateUnbounded<SendMessage>();
var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
await sseTransport.StartAsync(
new Uri("http://fakeuri.org"), channelConnection, connectionId: string.Empty).OrTimeout();
await eventStreamTcs.Task;
var sendTcs = new TaskCompletionSource<object>();
Assert.True(connectionToTransport.Out.TryWrite(new SendMessage(new byte[] { 0x42 }, sendTcs)));
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => sendTcs.Task.OrTimeout());
Assert.Contains("500", exception.Message);
Assert.Same(exception, await Assert.ThrowsAsync<HttpRequestException>(() => sseTransport.Running.OrTimeout()));
}
}
[Fact]
public async Task SSETransportStopsIfChannelClosed()
{
var eventStreamTcs = new TaskCompletionSource<object>();
var copyToAsyncTcs = new TaskCompletionSource<int>();
var mockHttpHandler = new Mock<HttpMessageHandler>();
mockHttpHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
{
await Task.Yield();
// Receive loop started - allow stopping the transport
eventStreamTcs.SetResult(null);
// returns unfinished task to block pipelines
var mockStream = new Mock<Stream>();
mockStream
.Setup(s => s.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Returns(copyToAsyncTcs.Task);
return new HttpResponseMessage { Content = new StreamContent(mockStream.Object) };
});
using (var httpClient = new HttpClient(mockHttpHandler.Object))
{
var sseTransport = new ServerSentEventsTransport(httpClient);
var connectionToTransport = Channel.CreateUnbounded<SendMessage>();
var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
await sseTransport.StartAsync(
new Uri("http://fakeuri.org"), channelConnection, connectionId: string.Empty).OrTimeout();
await eventStreamTcs.Task.OrTimeout();
connectionToTransport.Out.TryComplete(null);
await sseTransport.Running.OrTimeout();
}
}
[Fact]
public async Task SSETransportStopsIfTheServerClosesTheStream()
{
var mockHttpHandler = new Mock<HttpMessageHandler>();
mockHttpHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
{
await Task.Yield();
return new HttpResponseMessage { Content = new StringContent("data: 3:abc\r\n\r\n") };
});
using (var httpClient = new HttpClient(mockHttpHandler.Object))
{
var sseTransport = new ServerSentEventsTransport(httpClient);
var connectionToTransport = Channel.CreateUnbounded<SendMessage>();
var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
await sseTransport.StartAsync(
new Uri("http://fakeuri.org"), channelConnection, connectionId: string.Empty).OrTimeout();
var message = await transportToConnection.In.ReadAsync().AsTask().OrTimeout();
Assert.Equal("3:abc", Encoding.ASCII.GetString(message));
await sseTransport.Running.OrTimeout();
}
}
}
}

View File

@ -21,7 +21,7 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.SignalR.Tests
{
[CollectionDefinition(Name)]
public class EndToEndTestsCollection : ICollectionFixture<ServerFixture>
public class EndToEndTestsCollection : ICollectionFixture<ServerFixture<Startup>>
{
public const string Name = "EndToEndTests";
}
@ -29,9 +29,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests
[Collection(EndToEndTestsCollection.Name)]
public class EndToEndTests : LoggedTest
{
private readonly ServerFixture _serverFixture;
private readonly ServerFixture<Startup> _serverFixture;
public EndToEndTests(ServerFixture serverFixture, ITestOutputHelper output) : base(output)
public EndToEndTests(ServerFixture<Startup> serverFixture, ITestOutputHelper output) : base(output)
{
if (serverFixture == null)
{

View File

@ -1,26 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.SignalR.Tests
{
internal class ForwardingLoggerProvider : ILoggerProvider
{
private readonly ILoggerFactory _loggerFactory;
public ForwardingLoggerProvider(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public void Dispose()
{
}
public ILogger CreateLogger(string categoryName)
{
return _loggerFactory.CreateLogger(categoryName);
}
}
}

View File

@ -9,7 +9,6 @@ using System.Threading.Tasks.Channels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;

View File

@ -11,10 +11,11 @@
Remove when fixed.
-->
<HasRuntimeOutput>true</HasRuntimeOutput>
<RuntimeIdentifier Condition="'$(TargetFramework)' != 'netcoreapp2.0'">win7-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(TargetFramework)' != 'netcoreapp2.0'">win7-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\ServerFixture.cs" Link="ServerFixture.cs" />
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
</ItemGroup>

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.SignalR.Tests
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSockets();
services.AddSignalR();
services.AddEndPoint<EchoEndPoint>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSockets(options => options.MapEndPoint<EchoEndPoint>("echo"));
app.UseSignalR(options => options.MapHub<UncreatableHub>("uncreatable"));
}
}
}

View File

@ -17,9 +17,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests
[Collection(EndToEndTestsCollection.Name)]
public class WebSocketsTransportTests : LoggedTest
{
private readonly ServerFixture _serverFixture;
private readonly ServerFixture<Startup> _serverFixture;
public WebSocketsTransportTests(ServerFixture serverFixture, ITestOutputHelper output) : base(output)
public WebSocketsTransportTests(ServerFixture<Startup> serverFixture, ITestOutputHelper output) : base(output)
{
if (serverFixture == null)
{