End to end versioning tests (#2354)
This commit is contained in:
parent
5c633aa183
commit
6c927d093b
|
|
@ -0,0 +1,230 @@
|
|||
// 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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections.Client;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Microsoft.AspNetCore.SignalR.Tests;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
|
||||
{
|
||||
// Disable running server tests in parallel so server logs can accurately be captured per test
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public class HubProtocolVersionTestsCollection : ICollectionFixture<ServerFixture<VersionStartup>>
|
||||
{
|
||||
public const string Name = nameof(HubProtocolVersionTestsCollection);
|
||||
}
|
||||
|
||||
[Collection(HubProtocolVersionTestsCollection.Name)]
|
||||
public class HubProtocolVersionTests : VerifiableServerLoggedTest
|
||||
{
|
||||
public HubProtocolVersionTests(ServerFixture<VersionStartup> serverFixture, ITestOutputHelper output) : base(serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TransportTypes))]
|
||||
public async Task ClientUsingOldCallWithOriginalProtocol(HttpTransportType transportType)
|
||||
{
|
||||
using (StartVerifiableLog(out var loggerFactory, $"{nameof(ClientUsingOldCallWithOriginalProtocol)}_{transportType}"))
|
||||
{
|
||||
var connectionBuilder = new HubConnectionBuilder()
|
||||
.WithLoggerFactory(loggerFactory)
|
||||
.WithUrl(ServerFixture.Url + "/version", transportType);
|
||||
|
||||
var connection = connectionBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
await connection.StartAsync().OrTimeout();
|
||||
|
||||
var result = await connection.InvokeAsync<string>(nameof(VersionHub.Echo), "Hello World!").OrTimeout();
|
||||
|
||||
Assert.Equal("Hello World!", result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.DisposeAsync().OrTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TransportTypes))]
|
||||
public async Task ClientUsingOldCallWithNewProtocol(HttpTransportType transportType)
|
||||
{
|
||||
using (StartVerifiableLog(out var loggerFactory, $"{nameof(ClientUsingOldCallWithNewProtocol)}_{transportType}"))
|
||||
{
|
||||
var connectionBuilder = new HubConnectionBuilder()
|
||||
.WithLoggerFactory(loggerFactory)
|
||||
.WithUrl(ServerFixture.Url + "/version", transportType);
|
||||
connectionBuilder.Services.AddSingleton<IHubProtocol>(new VersionedJsonHubProtocol(1000));
|
||||
|
||||
var connection = connectionBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
await connection.StartAsync().OrTimeout();
|
||||
|
||||
var result = await connection.InvokeAsync<string>(nameof(VersionHub.Echo), "Hello World!").OrTimeout();
|
||||
|
||||
Assert.Equal("Hello World!", result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.DisposeAsync().OrTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TransportTypes))]
|
||||
public async Task ClientUsingNewCallWithNewProtocol(HttpTransportType transportType)
|
||||
{
|
||||
using (StartVerifiableLog(out var loggerFactory, $"{nameof(ClientUsingNewCallWithNewProtocol)}_{transportType}"))
|
||||
{
|
||||
var httpConnectionFactory = new HttpConnectionFactory(Options.Create(new HttpConnectionOptions
|
||||
{
|
||||
Url = new Uri(ServerFixture.Url + "/version"),
|
||||
Transports = transportType
|
||||
}), loggerFactory);
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
var proxyConnectionFactory = new ProxyConnectionFactory(httpConnectionFactory);
|
||||
|
||||
var connectionBuilder = new HubConnectionBuilder()
|
||||
.WithLoggerFactory(loggerFactory);
|
||||
connectionBuilder.Services.AddSingleton<IHubProtocol>(new VersionedJsonHubProtocol(1000));
|
||||
connectionBuilder.Services.AddSingleton<IConnectionFactory>(proxyConnectionFactory);
|
||||
|
||||
var connection = connectionBuilder.Build();
|
||||
connection.On("NewProtocolMethodClient", () =>
|
||||
{
|
||||
tcs.SetResult(null);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await connection.StartAsync().OrTimeout();
|
||||
|
||||
// Task should already have been awaited in StartAsync
|
||||
var connectionContext = await proxyConnectionFactory.ConnectTask.OrTimeout();
|
||||
|
||||
// Simulate a new call from the client
|
||||
var messageToken = new JObject
|
||||
{
|
||||
["type"] = int.MaxValue
|
||||
};
|
||||
|
||||
connectionContext.Transport.Output.Write(Encoding.UTF8.GetBytes(messageToken.ToString()));
|
||||
connectionContext.Transport.Output.Write(new[] { (byte)0x1e });
|
||||
await connectionContext.Transport.Output.FlushAsync().OrTimeout();
|
||||
|
||||
await tcs.Task.OrTimeout();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.DisposeAsync().OrTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TransportTypes))]
|
||||
public async Task ClientWithUnsupportedProtocolVersionDoesNotConnect(HttpTransportType transportType)
|
||||
{
|
||||
bool ExpectedErrors(WriteContext writeContext)
|
||||
{
|
||||
return writeContext.LoggerName == typeof(HubConnection).FullName;
|
||||
}
|
||||
|
||||
using (StartVerifiableLog(out var loggerFactory, LogLevel.Trace, $"{nameof(ClientWithUnsupportedProtocolVersionDoesNotConnect)}_{transportType}", expectedErrorsFilter: ExpectedErrors))
|
||||
{
|
||||
var connectionBuilder = new HubConnectionBuilder()
|
||||
.WithLoggerFactory(loggerFactory)
|
||||
.WithUrl(ServerFixture.Url + "/version", transportType);
|
||||
connectionBuilder.Services.AddSingleton<IHubProtocol>(new VersionedJsonHubProtocol(int.MaxValue));
|
||||
|
||||
var connection = connectionBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
await ExceptionAssert.ThrowsAsync<HubException>(
|
||||
() => connection.StartAsync(),
|
||||
"Unable to complete handshake with the server due to an error: The server does not support version 2147483647 of the 'json' protocol.").OrTimeout();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.DisposeAsync().OrTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyConnectionFactory : IConnectionFactory
|
||||
{
|
||||
private readonly IConnectionFactory _innerFactory;
|
||||
public Task<ConnectionContext> ConnectTask { get; private set; }
|
||||
|
||||
public ProxyConnectionFactory(IConnectionFactory innerFactory)
|
||||
{
|
||||
_innerFactory = innerFactory;
|
||||
}
|
||||
|
||||
public Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ConnectTask = _innerFactory.ConnectAsync(transferFormat, cancellationToken);
|
||||
return ConnectTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync(ConnectionContext connection)
|
||||
{
|
||||
return _innerFactory.DisposeAsync(connection);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> TransportTypes()
|
||||
{
|
||||
if (TestHelpers.IsWebSocketsSupported())
|
||||
{
|
||||
yield return new object[] { HttpTransportType.WebSockets };
|
||||
}
|
||||
yield return new object[] { HttpTransportType.ServerSentEvents };
|
||||
yield return new object[] { HttpTransportType.LongPolling };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,6 +178,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
|
|||
Task NoClientHandler();
|
||||
}
|
||||
|
||||
public class VersionHub : Hub
|
||||
{
|
||||
public string Echo(string message) => message;
|
||||
|
||||
public Task NewProtocolMethodServer()
|
||||
{
|
||||
return Clients.Caller.SendAsync("NewProtocolMethodClient");
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
|
||||
public class HubWithAuthorization : Hub
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
|
||||
{
|
||||
public class VersionedJsonHubProtocol : IHubProtocol
|
||||
{
|
||||
private readonly int _version;
|
||||
private readonly JsonHubProtocol _innerProtocol;
|
||||
|
||||
public VersionedJsonHubProtocol(int version)
|
||||
{
|
||||
_version = version;
|
||||
_innerProtocol = new JsonHubProtocol();
|
||||
}
|
||||
|
||||
public string Name => _innerProtocol.Name;
|
||||
public int Version => _version;
|
||||
public TransferFormat TransferFormat => _innerProtocol.TransferFormat;
|
||||
|
||||
public bool TryParseMessage(ref ReadOnlySequence<byte> input, IInvocationBinder binder, out HubMessage message)
|
||||
{
|
||||
var inputCopy = input;
|
||||
if (!TryParseMessage(ref input, out var payload))
|
||||
{
|
||||
message = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle "new" call
|
||||
var json = Encoding.UTF8.GetString(payload.ToArray());
|
||||
var o = JObject.Parse(json);
|
||||
if ((int)o["type"] == int.MaxValue)
|
||||
{
|
||||
message = new InvocationMessage("NewProtocolMethodServer", Array.Empty<object>());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle "old" calls
|
||||
var result = _innerProtocol.TryParseMessage(ref inputCopy, binder, out message);
|
||||
input = inputCopy;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool TryParseMessage(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> payload)
|
||||
{
|
||||
var position = buffer.PositionOf((byte)0x1e);
|
||||
if (position == null)
|
||||
{
|
||||
payload = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = buffer.Slice(0, position.Value);
|
||||
|
||||
// Skip record separator
|
||||
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
|
||||
{
|
||||
_innerProtocol.WriteMessage(message, output);
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
|
||||
{
|
||||
return _innerProtocol.GetMessageBytes(message);
|
||||
}
|
||||
|
||||
public bool IsVersionSupported(int version)
|
||||
{
|
||||
// Support older clients
|
||||
return version <= _version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// 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.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
|
||||
{
|
||||
public class VersionStartup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSignalR(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = true;
|
||||
});
|
||||
|
||||
services.RemoveAll<IHubProtocol>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHubProtocol>(new VersionedJsonHubProtocol(1000)));
|
||||
|
||||
services.AddAuthentication();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseSignalR(routes =>
|
||||
{
|
||||
routes.MapHub<VersionHub>("/version");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue