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

View File

@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
case ServerSentEventsMessageParser.ParseResult.Incomplete: case ServerSentEventsMessageParser.ParseResult.Incomplete:
if (result.IsCompleted) if (result.IsCompleted)
{ {
throw new FormatException("Incomplete message"); throw new FormatException("Incomplete message.");
} }
break; 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging.Testing; 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 ILoggerFactory _loggerFactory;
private ILogger _logger; private ILogger _logger;
@ -28,37 +27,21 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public ServerFixture() public ServerFixture()
{ {
var testLog = AssemblyTestLog.ForAssembly(typeof(ServerFixture).Assembly); var testLog = AssemblyTestLog.ForAssembly(typeof(ServerFixture<TStartup>).Assembly);
_logToken = testLog.StartTestLog(null, typeof(ServerFixture).FullName, out _loggerFactory, "ServerFixture"); _logToken = testLog.StartTestLog(null, typeof(ServerFixture<TStartup>).FullName, out _loggerFactory, "ServerFixture");
_logger = _loggerFactory.CreateLogger<ServerFixture>(); _logger = _loggerFactory.CreateLogger<ServerFixture<TStartup>>();
StartServer(); 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() private void StartServer()
{ {
host = new WebHostBuilder() host = new WebHostBuilder()
.ConfigureLogging(builder => builder.AddProvider(new ForwardingLoggerProvider(_loggerFactory))) .ConfigureLogging(builder => builder.AddProvider(new ForwardingLoggerProvider(_loggerFactory)))
.UseStartup(typeof(TStartup))
.UseKestrel() .UseKestrel()
.UseUrls(BaseUrl) .UseUrls(BaseUrl)
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build(); .Build();
var t = Task.Run(() => host.Start()); var t = Task.Run(() => host.Start());
@ -86,6 +69,26 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{ {
_logger.LogInformation("Shutting down test server"); _logger.LogInformation("Shutting down test server");
host.Dispose(); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Channels; using System.Threading.Tasks.Channels;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Logging.Testing;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xunit; using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{ {
public class HubConnectionTests : IDisposable [CollectionDefinition(Name)]
public class HubConnectionTestsCollection : ICollectionFixture<ServerFixture<Startup>>
{ {
private readonly TestServer _testServer; public const string Name = "EndToEndTests";
private static readonly bool _verbose = string.Equals(Environment.GetEnvironmentVariable("SIGNALR_TEST_VERBOSE"), "1"); }
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(). if (serverFixture == null)
ConfigureServices(services => {
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(); await connection.StartAsync().OrTimeout();
})
.ConfigureLogging(loggerFactory => var result = await connection.InvokeAsync<string>(nameof(TestHub.HelloWorld)).OrTimeout();
Assert.Equal("Hello World!", result);
}
catch (Exception ex)
{ {
if (_verbose) loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
{ throw;
loggerFactory.AddConsole(); }
} finally
})
.Configure(app =>
{ {
app.UseSignalR(routes => await connection.DisposeAsync().OrTimeout();
{ }
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();
} }
} }
[Theory] [Theory]
[MemberData(nameof(HubProtocols))] [MemberData(nameof(HubProtocolsXTransports))]
public async Task CanSendAndReceiveMessage(IHubProtocol protocol) public async Task CanSendAndReceiveMessage(IHubProtocol protocol, TransportType transport)
{ {
var loggerFactory = CreateLogger(); using (StartLog(out var loggerFactory))
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
{ {
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); var result = await connection.InvokeAsync<string>(nameof(TestHub.Echo), originalMessage).OrTimeout();
}
finally Assert.Equal(originalMessage, result);
{ }
await connection.DisposeAsync(); catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
} }
} }
[Theory] [Theory]
[MemberData(nameof(HubProtocols))] [MemberData(nameof(HubProtocolsXTransports))]
public async Task MethodsAreCaseInsensitive(IHubProtocol protocol) public async Task MethodsAreCaseInsensitive(IHubProtocol protocol, TransportType transport)
{ {
var loggerFactory = CreateLogger(); using (StartLog(out var loggerFactory))
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
{ {
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); var result = await connection.InvokeAsync<string>(nameof(TestHub.Echo).ToLowerInvariant(), originalMessage).OrTimeout();
}
finally Assert.Equal(originalMessage, result);
{ }
await connection.DisposeAsync(); catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
} }
} }
[Theory] [Theory]
[MemberData(nameof(HubProtocols))] [MemberData(nameof(HubProtocolsXTransports))]
public async Task CanInvokeClientMethodFromServer(IHubProtocol protocol) public async Task CanInvokeClientMethodFromServer(IHubProtocol protocol, TransportType transport)
{ {
var loggerFactory = CreateLogger(); using (StartLog(out var loggerFactory))
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
{ {
await connection.StartAsync(); const string originalMessage = "SignalR";
var tcs = new TaskCompletionSource<string>(); var httpConnection = new HttpConnection(new Uri(_serverFixture.BaseUrl + "/hubs"), transport, loggerFactory);
connection.On<string>("Echo", tcs.SetResult); 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()); await connection.InvokeAsync(nameof(TestHub.CallEcho), originalMessage).OrTimeout();
}
finally Assert.Equal(originalMessage, await tcs.Task.OrTimeout());
{ }
await connection.DisposeAsync().OrTimeout(); catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
} }
} }
[Theory] [Theory]
[MemberData(nameof(HubProtocols))] [MemberData(nameof(HubProtocolsXTransports))]
public async Task CanStreamClientMethodFromServer(IHubProtocol protocol) public async Task CanStreamClientMethodFromServer(IHubProtocol protocol, TransportType transport)
{ {
var loggerFactory = CreateLogger(); using (StartLog(out var loggerFactory))
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 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()); Assert.Equal(new[] { "a", "b", "c" }, results.ToArray());
} }
finally catch (Exception ex)
{ {
await connection.DisposeAsync().OrTimeout(); loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
} }
} }
[Theory] [Theory]
[MemberData(nameof(HubProtocols))] [MemberData(nameof(HubProtocolsXTransports))]
public async Task ServerClosesConnectionIfHubMethodCannotBeResolved(IHubProtocol hubProtocol) public async Task ServerClosesConnectionIfHubMethodCannotBeResolved(IHubProtocol hubProtocol, TransportType transport)
{ {
var loggerFactory = CreateLogger(); using (StartLog(out var loggerFactory))
var httpConnection = new HttpConnection(new Uri("http://test/hubs"), TransportType.LongPolling, loggerFactory, _testServer.CreateHandler());
var connection = new HubConnection(httpConnection, hubProtocol, loggerFactory);
try
{ {
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>( var ex = await Assert.ThrowsAnyAsync<Exception>(
async () => await connection.InvokeAsync("!@#$%")); async () => await connection.InvokeAsync("!@#$%")).OrTimeout();
Assert.Equal("Unknown hub method '!@#$%'", ex.Message); Assert.Equal("Unknown hub method '!@#$%'", ex.Message);
} }
finally catch (Exception ex)
{ {
await connection.DisposeAsync(); loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await connection.DisposeAsync().OrTimeout();
}
} }
} }
public static IEnumerable<object[]> HubProtocols() => public static IEnumerable<object []> HubProtocolsXTransports()
new[] {
foreach (var protocol in HubProtocols)
{ {
new object[] { new JsonHubProtocol(new JsonSerializer()) }, foreach (var transport in TransportTypes())
new object[] { new MessagePackHubProtocol() }, {
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(); if (WebsocketsSupported())
}
private static ILoggerFactory CreateLogger()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
{ {
builder.AddConsole(); // TODO: Currently we are always sending Text messages over websockets which does not work
builder.AddFilter<ConsoleLoggerProvider>(level => level >= (_verbose ? LogLevel.Trace : LogLevel.Error)); // with binary protocols. It is getting fixed separately.
builder.AddDebug(); // 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;
return serviceCollection.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
}
public class TestHub : Hub
{
public string HelloWorld()
{
return "Hello World!";
} }
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) return true;
{
await Clients.Client(Context.ConnectionId).InvokeAsync("Echo", message);
}
public IObservable<string> Stream()
{
return new[] { "a", "b", "c" }.ToObservable();
} }
} }
} }

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

@ -5,9 +5,12 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Common\ServerFixture.cs" Link="ServerFixture.cs" />
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" /> <Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
<Compile Include="..\Common\ChannelExtensions.cs" Link="ChannelExtensions.cs" /> <Compile Include="..\Common\ChannelExtensions.cs" Link="ChannelExtensions.cs" />
</ItemGroup> </ItemGroup>
@ -22,8 +25,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" /> <PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
<PackageReference Include="xunit" 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.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Internal; using Microsoft.AspNetCore.Sockets.Internal;
using Microsoft.Extensions.Logging;
using Moq; using Moq;
using Moq.Protected; using Moq.Protected;
using Xunit; using Xunit;

View File

@ -4,9 +4,12 @@
using System; using System;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Channels; using System.Threading.Tasks.Channels;
using Microsoft.AspNetCore.Client.Tests;
using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Internal; using Microsoft.AspNetCore.Sockets.Internal;
@ -61,5 +64,219 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
copyToAsyncTcs.SetResult(0); 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 namespace Microsoft.AspNetCore.SignalR.Tests
{ {
[CollectionDefinition(Name)] [CollectionDefinition(Name)]
public class EndToEndTestsCollection : ICollectionFixture<ServerFixture> public class EndToEndTestsCollection : ICollectionFixture<ServerFixture<Startup>>
{ {
public const string Name = "EndToEndTests"; public const string Name = "EndToEndTests";
} }
@ -29,9 +29,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests
[Collection(EndToEndTestsCollection.Name)] [Collection(EndToEndTestsCollection.Name)]
public class EndToEndTests : LoggedTest 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) 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.Authorization;
using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;

View File

@ -15,6 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Common\ServerFixture.cs" Link="ServerFixture.cs" />
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" /> <Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
</ItemGroup> </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)] [Collection(EndToEndTestsCollection.Name)]
public class WebSocketsTransportTests : LoggedTest 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) if (serverFixture == null)
{ {