From 693367c1ba22f9eff8554c987dc61c0283386caa Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 22 Aug 2019 14:32:37 +0800 Subject: Add user detail controller. --- Timeline.Tests/IntegratedTests/UserDetailTest.cs | 123 +++++++++++++++++++++++ Timeline/Controllers/UserDetailController.cs | 75 ++++++++++++++ Timeline/Services/UserDetailService.cs | 9 ++ Timeline/Startup.cs | 1 + 4 files changed, 208 insertions(+) create mode 100644 Timeline.Tests/IntegratedTests/UserDetailTest.cs create mode 100644 Timeline/Controllers/UserDetailController.cs diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs new file mode 100644 index 00000000..571f200f --- /dev/null +++ b/Timeline.Tests/IntegratedTests/UserDetailTest.cs @@ -0,0 +1,123 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Timeline.Controllers; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Tests.Helpers; +using Timeline.Tests.Helpers.Authentication; +using Timeline.Tests.Mock.Data; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests.IntegratedTests +{ + public class UserDetailTest : IClassFixture>, IDisposable + { + private readonly WebApplicationFactory _factory; + private readonly Action _disposeAction; + + public UserDetailTest(MyWebApplicationFactory factory, ITestOutputHelper outputHelper) + { + _factory = factory.WithTestConfig(outputHelper, out _disposeAction); + } + + public void Dispose() + { + _disposeAction(); + } + + [Fact] + public async Task TestAsUser() + { + using (var client = await _factory.CreateClientAsUser()) + { + { + var res = await client.GetAsync($"users/usernotexist/details"); + res.Should().HaveStatusCodeNotFound() + .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.Get_UserNotExist); + } + + async Task GetAndTest(UserDetail d) + { + var res = await client.GetAsync($"users/{MockUsers.UserUsername}/details"); + res.Should().HaveStatusCodeOk() + .And.Should().HaveBodyAsJson() + .Which.Should().BeEquivalentTo(d); + } + + await GetAndTest(new UserDetail()); + + { + var res = await client.PatchAsJsonAsync($"users/{MockUsers.AdminUsername}/details", new UserDetail()); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden) + .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.Patch_Forbid); + } + + { + var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", new UserDetail + { + QQ = "aaaaaaa", + EMail = "aaaaaa" + }); + res.Should().HaveStatusCode(HttpStatusCode.BadRequest) + .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.InvalidModel); + } + + var detail = new UserDetail + { + QQ = "1234567", + EMail = "aaaa@aaa.net", + Description = "aaaaaaaaa" + }; + + { + var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", detail); + res.Should().HaveStatusCodeOk(); + await GetAndTest(detail); + } + + var detail2 = new UserDetail + { + QQ = "", + PhoneNumber = "12345678910", + Description = "bbbbbbbb" + }; + + { + var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", detail2); + res.Should().HaveStatusCodeOk(); + await GetAndTest(new UserDetail + { + QQ = null, + EMail = detail.EMail, + PhoneNumber = detail2.PhoneNumber, + Description = detail2.Description + }); + } + } + } + + [Fact] + public async Task TestAsAdmin() + { + using (var client = await _factory.CreateClientAsAdmin()) + { + { + var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", new UserDetail()); + res.Should().HaveStatusCodeOk(); + } + + { + var res = await client.PatchAsJsonAsync($"users/usernotexist/details", new UserDetail()); + res.Should().HaveStatusCodeNotFound() + .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.Patch_UserNotExist); + } + } + } + } +} \ No newline at end of file diff --git a/Timeline/Controllers/UserDetailController.cs b/Timeline/Controllers/UserDetailController.cs new file mode 100644 index 00000000..9e1d5483 --- /dev/null +++ b/Timeline/Controllers/UserDetailController.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using Timeline.Authenticate; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Services; + +namespace Timeline.Controllers +{ + [Route("users/{username}/details")] + [ProducesErrorResponseType(typeof(CommonResponse))] + [ApiController] + public class UserDetailController : Controller + { + public static class ErrorCodes + { + public const int Get_UserNotExist = -1001; + + public const int Patch_Forbid = -2001; + public const int Patch_UserNotExist = -2002; + + } + + private readonly ILogger _logger; + private readonly IUserDetailService _service; + + public UserDetailController(ILogger logger, IUserDetailService service) + { + _logger = logger; + _service = service; + } + + [HttpGet()] + [UserAuthorize] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserDetail))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Get([FromRoute] string username) + { + try + { + var detail = await _service.GetUserDetail(username); + return Ok(detail); + } + catch (UserNotExistException) + { + return NotFound(new CommonResponse(ErrorCodes.Get_UserNotExist, "The user does not exist.")); + } + } + + [HttpPatch()] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(void))] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Patch([FromRoute] string username, [FromBody] UserDetail detail) + { + if (!User.IsAdmin() && User.Identity.Name != username) + return StatusCode(StatusCodes.Status403Forbidden, new CommonResponse(ErrorCodes.Patch_Forbid, "You can't change other's details unless you are admin.")); + + try + { + await _service.UpdateUserDetail(username, detail); + return Ok(); + } + catch (UserNotExistException) + { + return NotFound(new CommonResponse(ErrorCodes.Patch_UserNotExist, "The user does not exist.")); + } + } + } +} diff --git a/Timeline/Services/UserDetailService.cs b/Timeline/Services/UserDetailService.cs index d1fdc040..0bb745f3 100644 --- a/Timeline/Services/UserDetailService.cs +++ b/Timeline/Services/UserDetailService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Linq; @@ -89,4 +90,12 @@ namespace Timeline.Services _logger.LogInformation("An entity is updated in user_details."); } } + + public static class UserDetailServiceCollectionExtensions + { + public static void AddUserDetailService(this IServiceCollection services) + { + services.AddScoped(); + } + } } diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index 66f648c3..b5a5106b 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -46,6 +46,7 @@ namespace Timeline services.AddTransient(); services.AddUserAvatarService(); + services.AddUserDetailService(); var databaseConfig = Configuration.GetSection(nameof(DatabaseConfig)).Get(); -- cgit v1.2.3