Adding tests for SSE transport #413
This commit is contained in:
parent
bcefbae00c
commit
2854e868ec
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)" />
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue