From 27e1360ffe4b2f5aa7d240e1de88c5459587b489 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 8 Apr 2022 17:39:07 +0800 Subject: ... --- .../Helpers/CommonJsonSerializeOptions.cs | 21 ++++ .../IntegratedTests/CommonJsonSerializeOptions.cs | 21 ---- .../IntegratedTests/HttpClientTestExtensions.cs | 3 +- .../IntegratedTests2/HttpClientTestExtensions.cs | 52 ++++++++ .../IntegratedTests2/IntegratedTestBase.cs | 133 +++++++++++++++++++++ .../IntegratedTests2/TimelineTest.cs | 34 ++++++ BackEnd/Timeline.Tests/Timeline.Tests.csproj | 6 + .../Timeline/Controllers/TimelineV2Controller.cs | 18 +-- 8 files changed, 259 insertions(+), 29 deletions(-) create mode 100644 BackEnd/Timeline.Tests/Helpers/CommonJsonSerializeOptions.cs delete mode 100644 BackEnd/Timeline.Tests/IntegratedTests/CommonJsonSerializeOptions.cs create mode 100644 BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs create mode 100644 BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs create mode 100644 BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs (limited to 'BackEnd') diff --git a/BackEnd/Timeline.Tests/Helpers/CommonJsonSerializeOptions.cs b/BackEnd/Timeline.Tests/Helpers/CommonJsonSerializeOptions.cs new file mode 100644 index 00000000..a7d6d01f --- /dev/null +++ b/BackEnd/Timeline.Tests/Helpers/CommonJsonSerializeOptions.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Timeline.Models.Converters; + +namespace Timeline.Tests.Helpers +{ + public static class CommonJsonSerializeOptions + { + public static JsonSerializerOptions Options { get; } + + static CommonJsonSerializeOptions() + { + Options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + Options.Converters.Add(new JsonStringEnumConverter()); + Options.Converters.Add(new JsonDateTimeConverter()); + } + } +} diff --git a/BackEnd/Timeline.Tests/IntegratedTests/CommonJsonSerializeOptions.cs b/BackEnd/Timeline.Tests/IntegratedTests/CommonJsonSerializeOptions.cs deleted file mode 100644 index a14c8eef..00000000 --- a/BackEnd/Timeline.Tests/IntegratedTests/CommonJsonSerializeOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Timeline.Models.Converters; - -namespace Timeline.Tests.IntegratedTests -{ - public static class CommonJsonSerializeOptions - { - public static JsonSerializerOptions Options { get; } - - static CommonJsonSerializeOptions() - { - Options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - Options.Converters.Add(new JsonStringEnumConverter()); - Options.Converters.Add(new JsonDateTimeConverter()); - } - } -} diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs b/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs index 9848564f..c66baafc 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs @@ -6,7 +6,8 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Threading.Tasks; using Timeline.Models.Http; - +using Timeline.Tests.Helpers; + namespace Timeline.Tests.IntegratedTests { public delegate void HeaderSetup(HttpRequestHeaders requestHeaders, HttpContentHeaders? contentHeaders); diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs b/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs new file mode 100644 index 00000000..0124b72a --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs @@ -0,0 +1,52 @@ +using FluentAssertions; +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using Timeline.Models.Http; +using Timeline.Tests.Helpers; + +namespace Timeline.Tests.IntegratedTests2 +{ + public delegate Task RequestSetupAsync(HttpRequestMessage httpRequest); + + public static class HttpClientTestExtensions + { + public static async Task TestSendAsync(this HttpClient client, HttpMethod method, string url, HttpContent? body = null, HttpStatusCode expectedStatusCode = HttpStatusCode.OK, RequestSetupAsync? requestSetup = null) + { + using var req = new HttpRequestMessage + { + Method = method, + RequestUri = new Uri(url, UriKind.Relative), + Content = body + }; + var task = requestSetup?.Invoke(req); + if (task is not null) await task; + var res = await client.SendAsync(req); + res.StatusCode.Should().Be(expectedStatusCode); + return res; + } + + public static async Task AssertJsonBodyAsync(HttpResponseMessage response) + { + var body = await response.Content.ReadFromJsonAsync(CommonJsonSerializeOptions.Options); + body.Should().NotBeNull($"Body is not json format of type {typeof(T).FullName}"); + return body!; + } + + public static async Task TestJsonSendAsync(this HttpClient client, HttpMethod method, string url, object? jsonBody = null, HttpStatusCode expectedStatusCode = HttpStatusCode.OK, RequestSetupAsync? requestSetup = null) + { + using JsonContent? reqContent = jsonBody is null ? null : JsonContent.Create(jsonBody, options: CommonJsonSerializeOptions.Options); + await client.TestSendAsync(method, url, reqContent, expectedStatusCode, requestSetup); + } + + public static async Task TestJsonSendAsync(this HttpClient client, HttpMethod method, string url, object? jsonBody = null, HttpStatusCode expectedStatusCode = HttpStatusCode.OK, RequestSetupAsync? requestSetup = null) + { + using JsonContent? reqContent = jsonBody == null ? null : JsonContent.Create(jsonBody, options: CommonJsonSerializeOptions.Options); + var res = await client.TestSendAsync(method, url, reqContent, expectedStatusCode, requestSetup); + var resBody = await AssertJsonBodyAsync(res); + return resBody; + } + } +} diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs new file mode 100644 index 00000000..1d01fd0e --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs @@ -0,0 +1,133 @@ +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Timeline.Models.Http; +using Timeline.Services.User; +using Timeline.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests.IntegratedTests2 +{ + public abstract class IntegratedTestBase : IAsyncLifetime + { + protected TestApplication TestApp { get; } + + protected int TestUserCount { get; } + + protected string NormalUserToken { get; set; } = default!; + protected string AdminUserToken { get; set; } = default!; + + public IntegratedTestBase(ITestOutputHelper testOutputHelper) : this(1, testOutputHelper) + { + + } + + public IntegratedTestBase(int userCount, ITestOutputHelper testOutputHelper) + { + if (userCount < 0) + throw new ArgumentOutOfRangeException(nameof(userCount), userCount, "User count can't be negative."); + + TestUserCount = userCount; + + TestApp = new TestApplication(testOutputHelper); + } + + protected virtual Task OnInitializeAsync() + { + return Task.CompletedTask; + } + + protected virtual Task OnDisposeAsync() + { + return Task.CompletedTask; + } + + protected virtual void OnInitialize() + { + + } + + protected virtual void OnDispose() + { + + } + + private async Task CreateInitUsersAsync() + { + using var scope = TestApp.Host.Services.CreateScope(); + + var userService = scope.ServiceProvider.GetRequiredService(); + var userPermissionService = scope.ServiceProvider.GetRequiredService(); + + var admin = await userService.CreateUserAsync(new CreateUserParams("admin", "adminpw")); + foreach (var permission in Enum.GetValues()) + { + await userPermissionService.AddPermissionToUserAsync(admin.Id, permission); + } + await userService.CreateUserAsync(new CreateUserParams("user", "userpw")); + } + + public async Task CreateUserAsync(string username, string password) + { + using var scope = TestApp.Host.Services.CreateScope(); + var userService = scope.ServiceProvider.GetRequiredService(); + await userService.CreateUserAsync(new CreateUserParams(username, password)); + } + + public async Task InitializeAsync() + { + await TestApp.InitializeAsync(); + await CreateInitUsersAsync(); + NormalUserToken = await CreateTokenWithCredentialAsync("user", "userpw"); + AdminUserToken = await CreateTokenWithCredentialAsync("admin", "adminpw"); + await OnInitializeAsync(); + OnInitialize(); + } + + public async Task DisposeAsync() + { + await OnDisposeAsync(); + OnDispose(); + await TestApp.DisposeAsync(); + } + + public HttpClient CreateDefaultClient(bool setApiBase = true) + { + var client = TestApp.Host.GetTestServer().CreateClient(); + if (setApiBase) + { + client.BaseAddress = new Uri(client.BaseAddress!, "api/"); + } + return client; + } + + public async Task CreateTokenWithCredentialAsync(string username, string password) + { + var client = CreateDefaultClient(); + var res = await client.TestJsonSendAsync(HttpMethod.Post, "token/create", + new HttpCreateTokenRequest { Username = username, Password = password }); + return res.Token; + } + + public HttpClient CreateClientWithToken(string token, bool setApiBase = true) + { + var client = CreateDefaultClient(setApiBase); + client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); + return client; + } + + public HttpClient CreateClientAsAdmin() + { + return CreateClientWithToken(AdminUserToken); + } + + public HttpClient CreateClientAsUser() + { + return CreateClientWithToken(NormalUserToken); + } + } +} diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs new file mode 100644 index 00000000..19ad315e --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs @@ -0,0 +1,34 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using Timeline.Models.Http; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests.IntegratedTests2 +{ + public class TimelineTest : IntegratedTestBase + { + public TimelineTest(ITestOutputHelper testOutput) : base(testOutput) + { + } + + [Fact] + public async Task CreateAndGet() + { + using var client = CreateClientAsUser(); + var a = await client.TestJsonSendAsync(HttpMethod.Post, "v2/timelines", new HttpTimelineCreateRequest + { + Name = "hello" + }, expectedStatusCode: HttpStatusCode.Created); + + var b = await client.TestJsonSendAsync(HttpMethod.Get, "v2/timelines/user/hello"); + + a.Name.Should().Be(b.Name); + a.UniqueId.Should().Be(b.UniqueId); + } + } +} + diff --git a/BackEnd/Timeline.Tests/Timeline.Tests.csproj b/BackEnd/Timeline.Tests/Timeline.Tests.csproj index 82dc4d6a..4d4d3da2 100644 --- a/BackEnd/Timeline.Tests/Timeline.Tests.csproj +++ b/BackEnd/Timeline.Tests/Timeline.Tests.csproj @@ -29,4 +29,10 @@ + + + + + + diff --git a/BackEnd/Timeline/Controllers/TimelineV2Controller.cs b/BackEnd/Timeline/Controllers/TimelineV2Controller.cs index c82fde2b..63beb357 100644 --- a/BackEnd/Timeline/Controllers/TimelineV2Controller.cs +++ b/BackEnd/Timeline/Controllers/TimelineV2Controller.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Timeline.Entities; using Timeline.Models.Http; using Timeline.Models.Validation; using Timeline.Services.Mapper; @@ -16,23 +17,26 @@ namespace Timeline.Controllers { private ITimelineService _timelineService; private IGenericMapper _mapper; - private TimelineMapper _timelineMapper; private IUserService _userService; - public TimelineV2Controller(ITimelineService timelineService, IGenericMapper mapper, TimelineMapper timelineMapper, IUserService userService) + public TimelineV2Controller(ITimelineService timelineService, IGenericMapper mapper, IUserService userService) { _timelineService = timelineService; _mapper = mapper; - _timelineMapper = timelineMapper; _userService = userService; } + private Task MapAsync(TimelineEntity entity) + { + return _mapper.MapAsync(entity, Url, User); + } + [HttpGet("{owner}/{timeline}")] public async Task> GetAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline) { var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); var t = await _timelineService.GetTimelineAsync(timelineId); - return await _timelineMapper.MapAsync(t, Url, User); + return await MapAsync(t); } [HttpPatch("{owner}/{timeline}")] @@ -51,7 +55,7 @@ namespace Timeline.Controllers } await _timelineService.ChangePropertyAsync(timelineId, _mapper.AutoMapperMap(body)); var t = await _timelineService.GetTimelineAsync(timelineId); - return await _timelineMapper.MapAsync(t, Url, User); + return await MapAsync(t); } [HttpDelete("{owner}/{timeline}")] @@ -123,8 +127,8 @@ namespace Timeline.Controllers var authUserId = GetAuthUserId(); var authUser = await _userService.GetUserAsync(authUserId); var timeline = await _timelineService.CreateTimelineAsync(authUserId, body.Name); - var result = await _timelineMapper.MapAsync(timeline, Url, User); - return CreatedAtAction(nameof(GetAsync), new { owner = authUser.Username, timeline = body.Name }, result); + var result = await MapAsync(timeline); + return CreatedAtAction("Get", new { owner = authUser.Username, timeline = body.Name }, result); } } } -- cgit v1.2.3