From 49b0cb7b465561447b8d4693ba988b13e0e1b57a Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 15 May 2021 16:01:21 +0800 Subject: feat: Timeline post change notification with signalr. --- BackEnd/Timeline.Tests/Helpers/TestApplication.cs | 7 ++ .../IntegratedTests/IntegratedTestBase.cs | 16 +++++ .../IntegratedTests/TimelineHubTest.cs | 82 +++++++++++++++++++++ .../IntegratedTests/TimelinePostTest.cs | 2 +- BackEnd/Timeline.Tests/Timeline.Tests.csproj | 1 + BackEnd/Timeline.Tests/packages.lock.json | 83 +++++++++++++++++++++- 6 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs (limited to 'BackEnd/Timeline.Tests') diff --git a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs index e0db966c..723ef500 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Collections.Generic; using System.IO; @@ -46,5 +48,10 @@ namespace Timeline.Tests.Helpers Directory.Delete(WorkDirectory, true); } + + public TestServer Server + { + get => (TestServer)Host.Services.GetRequiredService(); + } } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 588f2f93..259ebfa1 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -105,6 +105,14 @@ namespace Timeline.Tests.IntegratedTests return Task.FromResult(client); } + public async Task CreateTokenWithCredentialAsync(string username, string password) + { + var client = await CreateDefaultClient(); + var res = await client.TestPostAsync("token/create", + new HttpCreateTokenRequest { Username = username, Password = password }); + return res.Token; + } + public async Task CreateClientWithCredential(string username, string password, bool setApiBase = true) { var client = await CreateDefaultClient(setApiBase); @@ -115,6 +123,14 @@ namespace Timeline.Tests.IntegratedTests return client; } + public Task CreateTokenAsync(int userNumber) + { + if (userNumber == 0) + return CreateTokenWithCredentialAsync("admin", "adminpw"); + else + return CreateTokenWithCredentialAsync($"user{userNumber}", $"user{userNumber}pw"); + } + public Task CreateClientAs(int userNumber, bool setApiBase = true) { if (userNumber < 0) diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs new file mode 100644 index 00000000..66df74d7 --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs @@ -0,0 +1,82 @@ +using FluentAssertions; +using Microsoft.AspNetCore.SignalR.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.SignalRHub; +using Xunit; + +namespace Timeline.Tests.IntegratedTests +{ + public class TimelineHubTest : BaseTimelineTest + { + private HubConnection CreateConnection(string? token) + { + return new HubConnectionBuilder().WithUrl($"http://localhost/api/hub/timeline{(token is null ? "" : "?token=" + token)}", + options => options.HttpMessageHandlerFactory = _ => TestApp.Server.CreateHandler()).Build(); + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task TimelinePostUpdate_Should_Work(TimelineNameGenerator generator) + { + var token = await CreateTokenAsync(1); + + await using var connection = CreateConnection(token); + + var changed = false; + + connection.On(nameof(ITimelineClient.OnTimelinePostChanged), (timelineName) => + { + timelineName.Should().Be(generator(1)); + changed = true; + }); + + await connection.StartAsync(); + connection.State.Should().Be(HubConnectionState.Connected); + + using var client = await CreateClientAsUser(); + + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("aaa")); + changed.Should().BeFalse(); + + await connection.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), generator(1)); + + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("bbb")); + changed.Should().BeTrue(); + + changed = false; + + await connection.InvokeAsync(nameof(TimelineHub.UnsubscribeTimelinePostChange), generator(1)); + + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("ccc")); + changed.Should().BeFalse(); + } + + [Fact] + public async Task TimelinePostUpdate_InvalidName() + { + await using var connection = CreateConnection(null); + await connection.StartAsync(); + await connection.Awaiting(c => c.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), "!!!")).Should().ThrowAsync(); + } + + [Fact] + public async Task TimelinePostUpdate_NotExist() + { + await using var connection = CreateConnection(null); + await connection.StartAsync(); + await connection.Awaiting(c => c.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), "timelinenotexist")).Should().ThrowAsync(); + } + + [Fact] + public async Task TimelinePostUpdate_Forbid() + { + await using var connection = CreateConnection(null); + await connection.StartAsync(); + await connection.Awaiting(c => c.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), "t1")).Should().ThrowAsync(); + } + } +} + diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs index 097275b0..ab8f6f66 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -19,7 +19,7 @@ namespace Timeline.Tests.IntegratedTests { public class TimelinePostTest : BaseTimelineTest { - private static HttpTimelinePostCreateRequest CreateTextPostRequest(string text, DateTime? time = null, string? color = null) + public static HttpTimelinePostCreateRequest CreateTextPostRequest(string text, DateTime? time = null, string? color = null) { return new HttpTimelinePostCreateRequest() { diff --git a/BackEnd/Timeline.Tests/Timeline.Tests.csproj b/BackEnd/Timeline.Tests/Timeline.Tests.csproj index 4a07024c..127a6b9a 100644 --- a/BackEnd/Timeline.Tests/Timeline.Tests.csproj +++ b/BackEnd/Timeline.Tests/Timeline.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/BackEnd/Timeline.Tests/packages.lock.json b/BackEnd/Timeline.Tests/packages.lock.json index 6a445ddc..be424c80 100644 --- a/BackEnd/Timeline.Tests/packages.lock.json +++ b/BackEnd/Timeline.Tests/packages.lock.json @@ -27,6 +27,16 @@ "Newtonsoft.Json.Bson": "1.0.1" } }, + "Microsoft.AspNetCore.SignalR.Client": { + "type": "Direct", + "requested": "[5.0.6, )", + "resolved": "5.0.6", + "contentHash": "MgY6tg5iVaFwMRcwAQiUAp+BC+h1iGWb72SFBaIvryOc6tmCz/JrXzcS7P993W0mznhs+vHh+p2Nf+jo+dNLpw==", + "dependencies": { + "Microsoft.AspNetCore.Http.Connections.Client": "5.0.6", + "Microsoft.AspNetCore.SignalR.Client.Core": "5.0.6" + } + }, "Microsoft.AspNetCore.TestHost": { "type": "Direct", "requested": "[5.0.5, )", @@ -114,6 +124,41 @@ "resolved": "0.24.0", "contentHash": "xvoxO3Ck4mNflc+62d5LCI8xCVmj+SEdpGPptUZmPtbqK88iZkSxtprJWUadHNt8tXY6rFx8oHGpEw1Qcu3lAw==" }, + "Microsoft.AspNetCore.Connections.Abstractions": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "dqtwdQL7cUdWHiyCkVB/0LIpBtdhwnhh9AMCOf+0eBb3wCF5/YvKMU0oVtMJViy+PFSxwjM8RyaVyC9G0Gl4Rg==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "5.0.6" + } + }, + "Microsoft.AspNetCore.Http.Connections.Client": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "PchO5EMTZmOd/z13NrQXDQjifCIj5+wewe+/UENRakVPIPl+oKcnyFF0xTeJRBWc8DbIm4OdAr/ET/fqpUgKOg==", + "dependencies": { + "Microsoft.AspNetCore.Http.Connections.Common": "5.0.6", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0" + } + }, + "Microsoft.AspNetCore.Http.Connections.Common": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "9V35MkPVkKhFfhtc/tEROmyQ0k94qjZz97sia0rRDy5zxZuu2OTzQst+9oW+iujoQotpWAjZD+gRu3S04uetjQ==", + "dependencies": { + "Microsoft.AspNetCore.Connections.Abstractions": "5.0.6" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "MfpLQRQK/iBZLiaRgPd4dNq4REVbycYH9apU0i18UWQsqZt2Oa0+gZRnz8j4eDCzzCwQSoVXtuCtuCdEnIk9vg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "5.0.1", + "System.IO.Pipelines": "5.0.1" + } + }, "Microsoft.AspNetCore.JsonPatch": { "type": "Transitive", "resolved": "5.0.0", @@ -133,6 +178,35 @@ "Newtonsoft.Json.Bson": "1.0.2" } }, + "Microsoft.AspNetCore.SignalR.Client.Core": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "J2kWDD1xnHDYMNLVxZ5MarqAg/lygYYKwYoqPobAWDlCF1wpXbmO/dAgby0NScTrvzSd6+NdyHp7dgxz/whLnw==", + "dependencies": { + "Microsoft.AspNetCore.SignalR.Common": "5.0.6", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "5.0.6", + "Microsoft.Extensions.DependencyInjection": "5.0.1", + "Microsoft.Extensions.Logging": "5.0.0", + "System.Threading.Channels": "5.0.0" + } + }, + "Microsoft.AspNetCore.SignalR.Common": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "x10OXN4vP8dB7SaqEU+Te2tbKy5HAFMRfhbpHSijY16S3JDfp6HqzdWjAKjinmzEoawtCegedTJ4b3/eslQbOQ==", + "dependencies": { + "Microsoft.AspNetCore.Connections.Abstractions": "5.0.6", + "Microsoft.Extensions.Options": "5.0.0" + } + }, + "Microsoft.AspNetCore.SignalR.Protocols.Json": { + "type": "Transitive", + "resolved": "5.0.6", + "contentHash": "fFwax6INapN++2DGfxriYJm4lFDTXNyeoBa1qi2ppJqosQATLI2JpErDHIjZwSWqHLL3umxGNWGpQPDzSkIkVg==", + "dependencies": { + "Microsoft.AspNetCore.SignalR.Common": "5.0.6" + } + }, "Microsoft.AspNetCore.SpaServices.Extensions": { "type": "Transitive", "resolved": "5.0.5", @@ -322,8 +396,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "cI/VWn9G1fghXrNDagX9nYaaB/nokkZn0HYAawGaELQrl8InSezfe9OnfPZLcJq3esXxygh3hkq2c3qoV3SDyQ==" + "resolved": "5.0.1", + "contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw==" }, "Microsoft.IdentityModel.JsonWebTokens": { "type": "Transitive", @@ -1457,6 +1531,11 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Threading.Channels": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "RLBIxntLaG9pRmmuVDwY1kc8Bvp/FQzSxPU+19VekkScKkWtVP9r8bLhm28ama3usc816UBrmkg3vv3jUea/hw==" + }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", -- cgit v1.2.3