From 95efb6a6b53b0fc732effaa0ef0f1e5660c0e2b9 Mon Sep 17 00:00:00 2001 From: yzt Date: Wed, 28 Apr 2021 12:05:38 +0800 Subject: [PATCH] Add E2E test for external routing (#215) --- .../Properties/AssemblyInfo.cs | 1 + .../ExtenalRouting/ExternalRoutingClient.cs | 28 +++++++ .../ExtenalRouting/ExternalRoutingTests.cs | 83 +++++++++++++++++++ ....Extensions.SignalRService.E2ETests.csproj | 1 + .../Utils.cs | 5 +- .../appsettings.json | 2 + .../external-routing/js/.gitignore | 43 ++++++++++ .../external-routing/js/extensions.csproj | 17 ++++ .../external-routing/js/host.json | 11 +++ .../js/negotiate/function.json | 26 ++++++ .../external-routing/js/negotiate/index.js | 5 ++ .../external-routing/js/send/function.json | 31 +++++++ .../external-routing/js/send/index.js | 12 +++ 13 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingClient.cs create mode 100644 test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingTests.cs create mode 100644 test/e2etest-functions/external-routing/js/.gitignore create mode 100644 test/e2etest-functions/external-routing/js/extensions.csproj create mode 100644 test/e2etest-functions/external-routing/js/host.json create mode 100644 test/e2etest-functions/external-routing/js/negotiate/function.json create mode 100644 test/e2etest-functions/external-routing/js/negotiate/index.js create mode 100644 test/e2etest-functions/external-routing/js/send/function.json create mode 100644 test/e2etest-functions/external-routing/js/send/index.js diff --git a/src/SignalRServiceExtension/Properties/AssemblyInfo.cs b/src/SignalRServiceExtension/Properties/AssemblyInfo.cs index 8a132e0..d071551 100644 --- a/src/SignalRServiceExtension/Properties/AssemblyInfo.cs +++ b/src/SignalRServiceExtension/Properties/AssemblyInfo.cs @@ -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")] \ No newline at end of file diff --git a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingClient.cs b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingClient.cs new file mode 100644 index 0000000..5c12fb8 --- /dev/null +++ b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingClient.cs @@ -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 Negotiate(string userId, string url, int endpointId) + { + const string path = "negotiate"; + var connectionInfo = await HttpClient.GetFromJsonAsync($"{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}"); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingTests.cs b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingTests.cs new file mode 100644 index 0000000..aa3ba0f --- /dev/null +++ b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/ExtenalRouting/ExternalRoutingTests.cs @@ -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 FunctionUrls = GetFunctionUrls(Section); + + /// + /// 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]. + /// + [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(); + 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); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests.csproj b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests.csproj index e84764f..eaf12a3 100644 --- a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests.csproj +++ b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests.csproj @@ -15,6 +15,7 @@ + diff --git a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Utils.cs b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Utils.cs index 969a2b2..761910b 100644 --- a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Utils.cs +++ b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/Utils.cs @@ -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) { diff --git a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/appsettings.json b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/appsettings.json index a1486a0..4729486 100644 --- a/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/appsettings.json +++ b/test/Microsoft.Azure.Webjobs.Extensions.SignalRService.E2ETests/appsettings.json @@ -1,6 +1,8 @@ { "FunctionBaseUrl": { "SimpleChat": { + }, + "ExternalRouting": { } } } \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/.gitignore b/test/e2etest-functions/external-routing/js/.gitignore new file mode 100644 index 0000000..fbbe2ef --- /dev/null +++ b/test/e2etest-functions/external-routing/js/.gitignore @@ -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 \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/extensions.csproj b/test/e2etest-functions/external-routing/js/extensions.csproj new file mode 100644 index 0000000..c2545ce --- /dev/null +++ b/test/e2etest-functions/external-routing/js/extensions.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + v3 + + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/host.json b/test/e2etest-functions/external-routing/js/host.json new file mode 100644 index 0000000..7a81a75 --- /dev/null +++ b/test/e2etest-functions/external-routing/js/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + } +} \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/negotiate/function.json b/test/e2etest-functions/external-routing/js/negotiate/function.json new file mode 100644 index 0000000..ee5945c --- /dev/null +++ b/test/e2etest-functions/external-routing/js/negotiate/function.json @@ -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%" + } + ] +} \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/negotiate/index.js b/test/e2etest-functions/external-routing/js/negotiate/index.js new file mode 100644 index 0000000..dd2c2ac --- /dev/null +++ b/test/e2etest-functions/external-routing/js/negotiate/index.js @@ -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; +} \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/send/function.json b/test/e2etest-functions/external-routing/js/send/function.json new file mode 100644 index 0000000..8637b65 --- /dev/null +++ b/test/e2etest-functions/external-routing/js/send/function.json @@ -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%" + } + ] +} \ No newline at end of file diff --git a/test/e2etest-functions/external-routing/js/send/index.js b/test/e2etest-functions/external-routing/js/send/index.js new file mode 100644 index 0000000..63c882f --- /dev/null +++ b/test/e2etest-functions/external-routing/js/send/index.js @@ -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(); +} \ No newline at end of file