Add E2E test for external routing (#215)
This commit is contained in:
Родитель
8c805ad858
Коммит
95efb6a6b5
|
@ -4,4 +4,5 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SignalRServiceExtension.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
|
||||
|
||||
namespace Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests
|
||||
{
|
||||
internal class ExternalRoutingClient
|
||||
{
|
||||
private static readonly HttpClient HttpClient = new();
|
||||
|
||||
public static async Task<SignalRConnectionInfo> Negotiate(string userId, string url, int endpointId)
|
||||
{
|
||||
const string path = "negotiate";
|
||||
var connectionInfo = await HttpClient.GetFromJsonAsync<SignalRConnectionInfo>($"{url}/api/{path}?userId={userId}&endpointId={endpointId}");
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
public static async Task Send(string url, int endpointId, string target)
|
||||
{
|
||||
const string SendPath = "send";
|
||||
await HttpClient.GetAsync($"{url}/api/{SendPath}?endpointId={endpointId}&target={target}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
|
||||
using Xunit;
|
||||
using static Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests.Utils;
|
||||
using Client = Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests.ExternalRoutingClient;
|
||||
|
||||
namespace Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests
|
||||
{
|
||||
public class ExternalRoutingTests
|
||||
{
|
||||
private const string Section = "ExternalRouting";
|
||||
|
||||
public static readonly IEnumerable<object[]> FunctionUrls = GetFunctionUrls(Section);
|
||||
|
||||
/// <summary>
|
||||
/// Set up two connections, each of which connects to a different endpoint.
|
||||
/// Trigger the function to send a message to endpoint[0]. The parameter is the endpoint itself. Validate the endpoint status in the connection[0].
|
||||
/// Repeat last step to the endpoint[1].
|
||||
/// </summary>
|
||||
[MemberData(nameof(FunctionUrls))]
|
||||
[SkipIfFunctionAbsent(Section)]
|
||||
public async Task RoutingTest(string key, string url)
|
||||
{
|
||||
var target = nameof(RoutingTest) + key;
|
||||
const int count = 2;
|
||||
var users = GenerateRandomUsers(count).ToArray();
|
||||
var completionSources = new ConcurrentDictionary<string, TaskCompletionSource>();
|
||||
var tasks = Enumerable.Range(0, count).Zip(users).Select(async (pair) =>
|
||||
{
|
||||
var (endpointId, user) = pair;
|
||||
var connectionInfo = await Client.Negotiate(user, url, endpointId);
|
||||
var connection = CreateHubConnection(connectionInfo.Url, connectionInfo.AccessToken);
|
||||
var taskCompleSource = new TaskCompletionSource();
|
||||
completionSources.TryAdd(user, taskCompleSource);
|
||||
connection.On(target, (LiteServiceEndpoint endpoint) =>
|
||||
{
|
||||
var expectedHost = new Uri(connectionInfo.Url).Host;
|
||||
var actualHost = new Uri(endpoint.Endpoint).Host;
|
||||
if (expectedHost.Equals(actualHost) && endpoint.Online)
|
||||
{
|
||||
completionSources[user].SetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
completionSources[user].SetException(new Exception($"Expected host:{expectedHost}, Actual host:{actualHost}"));
|
||||
}
|
||||
});
|
||||
await connection.StartAsync();
|
||||
return connection;
|
||||
}).ToArray();
|
||||
var connections = await Task.WhenAll(tasks);
|
||||
|
||||
// send a message to endpoint[0]
|
||||
await Client.Send(url, 0, target);
|
||||
await Task.WhenAny(completionSources.Values.Select(s => s.Task.OrTimeout()));
|
||||
|
||||
// received the correct messsage
|
||||
Assert.True(completionSources[users[0]].Task.IsCompletedSuccessfully);
|
||||
// not received messages yet
|
||||
Assert.False(completionSources[users[1]].Task.IsCompleted);
|
||||
|
||||
//reset
|
||||
foreach (var user in users)
|
||||
{
|
||||
completionSources[user] = new TaskCompletionSource();
|
||||
}
|
||||
|
||||
// send a message to endpoint[1]
|
||||
await Client.Send(url, 1, target);
|
||||
await Task.WhenAny(completionSources.Values.Select(s => s.Task.OrTimeout()));
|
||||
Assert.True(completionSources[users[1]].Task.IsCompletedSuccessfully);
|
||||
Assert.False(completionSources[users[0]].Task.IsCompleted);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualstudioPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="$(MicrosoftAzureSignalRManagement)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="$(MicrosoftAspNetCoreSignalRProtocolsNewtonsoftJson)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Xunit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests
|
||||
{
|
||||
|
@ -38,7 +39,9 @@ namespace Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests
|
|||
{
|
||||
return Task.FromResult(accessToken);
|
||||
};
|
||||
}).Build();
|
||||
})
|
||||
.AddNewtonsoftJsonProtocol()
|
||||
.Build();
|
||||
|
||||
public static async Task OrTimeout(this Task task, TimeSpan timeout = default)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"FunctionBaseUrl": {
|
||||
"SimpleChat": {
|
||||
},
|
||||
"ExternalRouting": {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
bin
|
||||
obj
|
||||
csx
|
||||
.vs
|
||||
edge
|
||||
Publish
|
||||
|
||||
*.user
|
||||
*.suo
|
||||
*.cscfg
|
||||
*.Cache
|
||||
project.lock.json
|
||||
|
||||
/packages
|
||||
/TestResults
|
||||
|
||||
/tools/NuGet.exe
|
||||
/App_Data
|
||||
/secrets
|
||||
/data
|
||||
.secrets
|
||||
appsettings.json
|
||||
local.settings.json
|
||||
|
||||
node_modules
|
||||
dist
|
||||
|
||||
# Local python packages
|
||||
.python_packages/
|
||||
|
||||
# Python Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\SignalRServiceExtension\Microsoft.Azure.WebJobs.Extensions.SignalRService.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"isEnabled": true,
|
||||
"excludedTypes": "Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"bindings": [
|
||||
{
|
||||
"authLevel": "function",
|
||||
"type": "httpTrigger",
|
||||
"direction": "in",
|
||||
"name": "req",
|
||||
"methods": [
|
||||
"get",
|
||||
"post"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"direction": "out",
|
||||
"name": "res"
|
||||
},
|
||||
{
|
||||
"type": "signalRNegotiation",
|
||||
"direction": "in",
|
||||
"name": "negoCtx",
|
||||
"userId": "{query.userId}",
|
||||
"hubName": "%hub%"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = async function (context, req, negoCtx) {
|
||||
var id = req.query.endpointId;
|
||||
var endpointNum = negoCtx.endpoints.length;
|
||||
context.res.body = negoCtx.endpoints[id % endpointNum].connectionInfo;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"bindings": [
|
||||
{
|
||||
"authLevel": "function",
|
||||
"type": "httpTrigger",
|
||||
"direction": "in",
|
||||
"name": "req",
|
||||
"methods": [
|
||||
"get",
|
||||
"post"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"direction": "out",
|
||||
"name": "res"
|
||||
},
|
||||
{
|
||||
"type": "signalREndpoints",
|
||||
"direction": "in",
|
||||
"name": "endpoints",
|
||||
"hubName": "%hub%"
|
||||
},
|
||||
{
|
||||
"type": "signalR",
|
||||
"direction": "out",
|
||||
"name": "signalRMessages",
|
||||
"hubName": "%hub%"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = async function (context, req, endpoints) {
|
||||
var id = req.query.endpointId;
|
||||
var target = req.query.target;
|
||||
var endpoint = endpoints[id % endpoints.length];
|
||||
context.bindings.signalRMessages = [{
|
||||
"target": target,
|
||||
"endpoints": [endpoint],
|
||||
"arguments": [endpoint]
|
||||
}];
|
||||
|
||||
context.done();
|
||||
}
|
Загрузка…
Ссылка в новой задаче