From a1a2eede6942aef7d8c20f9e7fb25f53b2b63d86 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 30 Jan 2020 21:15:18 +0800 Subject: ... --- Timeline.Tests/UsernameValidatorUnitTest.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'Timeline.Tests/UsernameValidatorUnitTest.cs') diff --git a/Timeline.Tests/UsernameValidatorUnitTest.cs b/Timeline.Tests/UsernameValidatorUnitTest.cs index e0f4633f..1a09d477 100644 --- a/Timeline.Tests/UsernameValidatorUnitTest.cs +++ b/Timeline.Tests/UsernameValidatorUnitTest.cs @@ -5,7 +5,6 @@ using Xunit; namespace Timeline.Tests { - [UseCulture("en")] public class UsernameValidatorUnitTest : IClassFixture { private readonly UsernameValidator _validator; -- cgit v1.2.3 From 49bd47fb0eb81a88cce135f7ff7c25637790e63b Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 30 Jan 2020 23:49:02 +0800 Subject: Finish reafctor, TODO: Database migration. --- Timeline.Tests/Helpers/TestApplication.cs | 12 +++++++++- .../IntegratedTests/IntegratedTestBase.cs | 2 +- .../IntegratedTests/PersonalTimelineTest.cs | 28 +++++++++++----------- Timeline.Tests/IntegratedTests/UserAvatarTest.cs | 2 +- Timeline.Tests/IntegratedTests/UserTest.cs | 8 +++---- Timeline.Tests/UsernameValidatorUnitTest.cs | 8 +------ Timeline/Controllers/ControllerAuthExtensions.cs | 2 +- Timeline/Controllers/UserAvatarController.cs | 2 +- Timeline/Controllers/UserController.cs | 18 +++++++------- Timeline/Entities/UserEntity.cs | 2 +- Timeline/Models/Http/UserInfo.cs | 21 +++++++++++----- Timeline/Models/Validation/NicknameValidator.cs | 2 +- Timeline/Services/TimelineService.cs | 2 +- Timeline/Services/UserService.cs | 2 ++ 14 files changed, 64 insertions(+), 47 deletions(-) (limited to 'Timeline.Tests/UsernameValidatorUnitTest.cs') diff --git a/Timeline.Tests/Helpers/TestApplication.cs b/Timeline.Tests/Helpers/TestApplication.cs index 14cafea3..bc5deeec 100644 --- a/Timeline.Tests/Helpers/TestApplication.cs +++ b/Timeline.Tests/Helpers/TestApplication.cs @@ -17,10 +17,20 @@ namespace Timeline.Tests.Helpers public TestApplication(WebApplicationFactory factory) { DatabaseConnection = new SqliteConnection("Data Source=:memory:;"); + DatabaseConnection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(DatabaseConnection) + .Options; + + using (var context = new DevelopmentDatabaseContext(options)) + { + context.Database.EnsureCreated(); + } Factory = factory.WithWebHostBuilder(builder => { - builder.ConfigureTestServices(services => + builder.ConfigureServices(services => { services.AddDbContext(options => { diff --git a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index af3e0c2f..242e96cd 100644 --- a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -63,7 +63,7 @@ namespace Timeline.Tests.IntegratedTests foreach (var user in users) { - userService.CreateUser(user); + userService.CreateUser(user).Wait(); userInfoList.Add(mapper.Map(user)); userInfoForAdminList.Add(mapper.Map(user)); } diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs index 5c472e52..d787d87d 100644 --- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs @@ -103,12 +103,12 @@ namespace Timeline.Tests.IntegratedTests } await AssertMembers(new List { UserInfoList[2] }); { - var res = await client.DeleteAsync("/users/users1/timeline/members/users2"); + var res = await client.DeleteAsync("/users/user1/timeline/members/user2"); res.Should().BeDelete(true); } await AssertEmptyMembers(); { - var res = await client.DeleteAsync("/users/users1/timeline/members/users2"); + var res = await client.DeleteAsync("/users/user1/timeline/members/users2"); res.Should().BeDelete(false); } await AssertEmptyMembers(); @@ -137,22 +137,22 @@ namespace Timeline.Tests.IntegratedTests } { - var res = await client.PutAsync("users/user1/timeline/member/user2", null); + var res = await client.PutAsync("users/user1/timeline/members/user2", null); res.Should().HaveStatusCode(opMemberUser); } { - var res = await client.DeleteAsync("users/user1/timeline/member/user2"); + var res = await client.DeleteAsync("users/user1/timeline/members/user2"); res.Should().HaveStatusCode(opMemberUser); } { - var res = await client.PutAsync("users/admin/timeline/member/user2", null); + var res = await client.PutAsync("users/admin/timeline/members/user2", null); res.Should().HaveStatusCode(opMemberAdmin); } { - var res = await client.DeleteAsync("users/admin/timeline/member/user2"); + var res = await client.DeleteAsync("users/admin/timeline/members/user2"); res.Should().HaveStatusCode(opMemberAdmin); } } @@ -244,7 +244,7 @@ namespace Timeline.Tests.IntegratedTests { using (var client = await CreateClientAsUser()) { - var res = await client.PutAsync("users/user/timeline/members/user2", null); + var res = await client.PutAsync("users/user1/timeline/members/user2", null); res.Should().HaveStatusCode(200); } @@ -283,7 +283,7 @@ namespace Timeline.Tests.IntegratedTests using (var client = await CreateClientAs(2)) { { // post as member - var res = await client.PostAsJsonAsync("users/user1/timeline/postop/create", + var res = await client.PostAsJsonAsync("users/user1/timeline/posts", new TimelinePostCreateRequest { Content = "aaa" }); res.Should().HaveStatusCode(200); } @@ -296,7 +296,7 @@ namespace Timeline.Tests.IntegratedTests async Task CreatePost(int userNumber) { using var client = await CreateClientAs(userNumber); - var res = await client.PostAsJsonAsync($"users/user1/timeline/postop/create", + var res = await client.PostAsJsonAsync($"users/user1/timeline/posts", new TimelinePostCreateRequest { Content = "aaa" }); return res.Should().HaveStatusCode(200) .And.HaveJsonBody() @@ -383,7 +383,7 @@ namespace Timeline.Tests.IntegratedTests .Which; body.Should().NotBeNull(); body.Content.Should().Be(mockContent); - body.Author.Should().Be(UserInfoList[1]); + body.Author.Should().BeEquivalentTo(UserInfoList[1]); createRes = body; } { @@ -402,9 +402,9 @@ namespace Timeline.Tests.IntegratedTests .And.HaveJsonBody() .Which; body.Should().NotBeNull(); - body.Content.Should().Be(mockContent); - body.Author.Should().Be(UserInfoList[1]); - body.Time.Should().Be(mockTime2); + body.Content.Should().Be(mockContent2); + body.Author.Should().BeEquivalentTo(UserInfoList[1]); + body.Time.Should().BeCloseTo(mockTime2, 1000); createRes2 = body; } { @@ -450,7 +450,7 @@ namespace Timeline.Tests.IntegratedTests var id2 = await CreatePost(now); { - var res = await client.GetAsync("users/user/timeline/posts"); + var res = await client.GetAsync("users/user1/timeline/posts"); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Select(p => p.Id).Should().Equal(id1, id2, id0); diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index 989207e2..67c2dd9a 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -49,7 +49,7 @@ namespace Timeline.Tests.IntegratedTests var env = Factory.Server.Host.Services.GetRequiredService(); var defaultAvatarData = await File.ReadAllBytesAsync(Path.Combine(env.ContentRootPath, "default-avatar.png")); - async Task GetReturnDefault(string username = "user") + async Task GetReturnDefault(string username = "user1") { var res = await client.GetAsync($"users/{username}/avatar"); res.Should().HaveStatusCode(200); diff --git a/Timeline.Tests/IntegratedTests/UserTest.cs b/Timeline.Tests/IntegratedTests/UserTest.cs index 4c2ccf7a..1b9733ff 100644 --- a/Timeline.Tests/IntegratedTests/UserTest.cs +++ b/Timeline.Tests/IntegratedTests/UserTest.cs @@ -44,7 +44,7 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsAdministrator(); var res = await client.GetAsync("/users"); res.Should().HaveStatusCode(200) - .And.HaveJsonBody() + .And.HaveJsonBody() .Which.Should().BeEquivalentTo(UserInfoForAdminList); } @@ -74,7 +74,7 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsAdministrator(); var res = await client.GetAsync($"/users/user1"); res.Should().HaveStatusCode(200) - .And.HaveJsonBody() + .And.HaveJsonBody() .Which.Should().BeEquivalentTo(UserInfoForAdminList[1]); } @@ -142,7 +142,7 @@ namespace Timeline.Tests.IntegratedTests { // Token should expire. - var res = await userClient.GetAsync("/users"); + var res = await userClient.GetAsync("/testing/auth/Authorize"); res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } @@ -198,7 +198,7 @@ namespace Timeline.Tests.IntegratedTests [Fact] public async Task Patch_NoAuth_Unauthorized() { - using var client = await CreateClientAsUser(); + using var client = await CreateDefaultClient(); var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Nickname = "aaa" }); res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } diff --git a/Timeline.Tests/UsernameValidatorUnitTest.cs b/Timeline.Tests/UsernameValidatorUnitTest.cs index 1a09d477..0f844452 100644 --- a/Timeline.Tests/UsernameValidatorUnitTest.cs +++ b/Timeline.Tests/UsernameValidatorUnitTest.cs @@ -21,12 +21,6 @@ namespace Timeline.Tests return message; } - [Fact] - public void Null() - { - FailAndMessage(null).Should().ContainEquivalentOf("null"); - } - [Fact] public void NotString() { @@ -58,6 +52,7 @@ namespace Timeline.Tests } [Theory] + [InlineData(null)] [InlineData("abc")] [InlineData("-abc")] [InlineData("_abc")] @@ -68,7 +63,6 @@ namespace Timeline.Tests [InlineData("a-b_c")] public void Success(string value) { - var (result, _) = _validator.Validate(value); result.Should().BeTrue(); } diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs index 90da8a93..34fd4d99 100644 --- a/Timeline/Controllers/ControllerAuthExtensions.cs +++ b/Timeline/Controllers/ControllerAuthExtensions.cs @@ -34,7 +34,7 @@ namespace Timeline.Controllers var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier); if (claim == null) - throw new InvalidOperationException("Failed to get user id because User has no NameIdentifier claim."); + return null; if (long.TryParse(claim.Value, out var value)) return value; diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index ab0ad8e7..2dd279a8 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -78,7 +78,7 @@ namespace Timeline.Controllers [HttpPut("users/{username}/avatar")] [Authorize] - [RequireContentLength] + [RequireContentType, RequireContentLength] [Consumes("image/png", "image/jpeg", "image/gif", "image/webp")] public async Task Put([FromRoute][Username] string username) { diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index 400a518c..fa73c6f9 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -42,7 +42,13 @@ namespace Timeline.Controllers { var users = await _userService.GetUsers(); var administrator = this.IsAdministrator(); - return Ok(users.Select(u => ConvertToUserInfo(u, administrator)).ToArray()); + // Note: the (object) explicit conversion. If not convert, + // then result is a IUserInfo array and JsonSerializer will + // treat all element as IUserInfo and deserialize only properties + // in IUserInfo. So we convert it to object to make an object + // array so that JsonSerializer use the runtime type. + var result = users.Select(u => (object)ConvertToUserInfo(u, administrator)).ToArray(); + return Ok(result); } [HttpGet("users/{username}")] @@ -106,15 +112,11 @@ namespace Timeline.Controllers [HttpDelete("users/{username}"), AdminAuthorize] public async Task> Delete([FromRoute][Username] string username) { - try - { - await _userService.DeleteUser(username); + var delete = await _userService.DeleteUser(username); + if (delete) return Ok(CommonDeleteResponse.Delete()); - } - catch (UserNotExistException) - { + else return Ok(CommonDeleteResponse.NotExist()); - } } [HttpPost("userop/createuser"), AdminAuthorize] diff --git a/Timeline/Entities/UserEntity.cs b/Timeline/Entities/UserEntity.cs index dae6979f..946c3fa2 100644 --- a/Timeline/Entities/UserEntity.cs +++ b/Timeline/Entities/UserEntity.cs @@ -29,7 +29,7 @@ namespace Timeline.Entities [Column("version"), Required] public long Version { get; set; } - [Column("nickname"), MaxLength(40)] + [Column("nickname"), MaxLength(100)] public string? Nickname { get; set; } public UserAvatarEntity? Avatar { get; set; } diff --git a/Timeline/Models/Http/UserInfo.cs b/Timeline/Models/Http/UserInfo.cs index 6029b8aa..62d989a2 100644 --- a/Timeline/Models/Http/UserInfo.cs +++ b/Timeline/Models/Http/UserInfo.cs @@ -29,21 +29,30 @@ namespace Timeline.Models.Http public bool Administrator { get; set; } } - public class UserInfoSetAvatarUrlAction : IMappingAction + public class UserInfoAvatarUrlValueResolver : IValueResolver { private readonly IActionContextAccessor _actionContextAccessor; private readonly IUrlHelperFactory _urlHelperFactory; - public UserInfoSetAvatarUrlAction(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) + public UserInfoAvatarUrlValueResolver() + { + } + + public UserInfoAvatarUrlValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) { _actionContextAccessor = actionContextAccessor; _urlHelperFactory = urlHelperFactory; } - public void Process(object source, IUserInfo destination, ResolutionContext context) + public string Resolve(User source, IUserInfo destination, string destMember, ResolutionContext context) { + if (_actionContextAccessor == null) + { + return $"/users/{destination.Username}/avatar"; + } + var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext); - destination.AvatarUrl = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController), new { destination.Username }); + return urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController), new { destination.Username }); } } @@ -51,8 +60,8 @@ namespace Timeline.Models.Http { public UserInfoAutoMapperProfile() { - CreateMap().AfterMap(); - CreateMap().AfterMap(); + CreateMap().ForMember(u => u.AvatarUrl, opt => opt.MapFrom()); + CreateMap().ForMember(u => u.AvatarUrl, opt => opt.MapFrom()); } } } diff --git a/Timeline/Models/Validation/NicknameValidator.cs b/Timeline/Models/Validation/NicknameValidator.cs index 53a2916b..1d6ab163 100644 --- a/Timeline/Models/Validation/NicknameValidator.cs +++ b/Timeline/Models/Validation/NicknameValidator.cs @@ -7,7 +7,7 @@ namespace Timeline.Models.Validation { protected override (bool, string) DoValidate(string value) { - if (value.Length > 10) + if (value.Length > 25) return (false, MessageTooLong); return (true, GetSuccessMessage()); diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 89936aa2..16402f3e 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -295,7 +295,7 @@ namespace Timeline.Services { if (entity.Content != null) // otherwise it is deleted { - var author = Mapper.Map(UserService.GetUserById(entity.AuthorId)); + var author = Mapper.Map(await UserService.GetUserById(entity.AuthorId)); posts.Add(new TimelinePostInfo { Id = entity.Id, diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 1197bb73..d2dc969e 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -300,12 +300,14 @@ namespace Timeline.Services var administrator = info.Administrator ?? false; var password = info.Password; + var nickname = info.Nickname; var newEntity = new UserEntity { Username = username, Password = _passwordService.HashPassword(password), Roles = UserRoleConvert.ToString(administrator), + Nickname = nickname, Version = 1 }; _databaseContext.Users.Add(newEntity); -- cgit v1.2.3