From 52acf41e331ddbd66befed4692c804b754ba7d5c Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 30 Jan 2020 20:26:52 +0800 Subject: ... --- .../Controllers/PersonalTimelineControllerTest.cs | 388 ------------------- Timeline.Tests/Controllers/TokenControllerTest.cs | 135 ------- Timeline.Tests/Controllers/UserControllerTest.cs | 228 ----------- .../Controllers/UserDetailControllerTest.cs | 98 ----- Timeline.Tests/DatabaseTest.cs | 63 --- Timeline.Tests/GlobalSuppressions.cs | 2 + Timeline.Tests/Helpers/MockUser.cs | 24 -- Timeline.Tests/Helpers/PrincipalHelper.cs | 23 -- Timeline.Tests/Helpers/ResponseAssertions.cs | 14 +- Timeline.Tests/Helpers/TestApplication.cs | 15 +- Timeline.Tests/Helpers/TestClock.cs | 15 - Timeline.Tests/Helpers/TestDatabase.cs | 89 ----- Timeline.Tests/Helpers/UseCultureAttribute.cs | 94 ----- .../IntegratedTests/AuthorizationTest.cs | 4 +- Timeline.Tests/IntegratedTests/I18nTest.cs | 59 --- .../IntegratedTests/IntegratedTestBase.cs | 117 ++++-- .../IntegratedTests/PersonalTimelineTest.cs | 37 +- Timeline.Tests/IntegratedTests/TokenTest.cs | 18 +- Timeline.Tests/IntegratedTests/UserAvatarTest.cs | 45 ++- Timeline.Tests/IntegratedTests/UserDetailTest.cs | 154 -------- Timeline.Tests/IntegratedTests/UserTest.cs | 427 ++++++++++++++------- Timeline.Tests/Services/UserAvatarServiceTest.cs | 280 -------------- Timeline.Tests/Services/UserDetailServiceTest.cs | 107 ------ Timeline.Tests/Services/UserTokenManagerTest.cs | 163 -------- Timeline.Tests/Timeline.Tests.csproj | 1 - 25 files changed, 440 insertions(+), 2160 deletions(-) delete mode 100644 Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs delete mode 100644 Timeline.Tests/Controllers/TokenControllerTest.cs delete mode 100644 Timeline.Tests/Controllers/UserControllerTest.cs delete mode 100644 Timeline.Tests/Controllers/UserDetailControllerTest.cs delete mode 100644 Timeline.Tests/DatabaseTest.cs delete mode 100644 Timeline.Tests/Helpers/MockUser.cs delete mode 100644 Timeline.Tests/Helpers/PrincipalHelper.cs delete mode 100644 Timeline.Tests/Helpers/TestClock.cs delete mode 100644 Timeline.Tests/Helpers/TestDatabase.cs delete mode 100644 Timeline.Tests/Helpers/UseCultureAttribute.cs delete mode 100644 Timeline.Tests/IntegratedTests/I18nTest.cs delete mode 100644 Timeline.Tests/IntegratedTests/UserDetailTest.cs delete mode 100644 Timeline.Tests/Services/UserAvatarServiceTest.cs delete mode 100644 Timeline.Tests/Services/UserDetailServiceTest.cs delete mode 100644 Timeline.Tests/Services/UserTokenManagerTest.cs (limited to 'Timeline.Tests') diff --git a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs deleted file mode 100644 index bbc8ba75..00000000 --- a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs +++ /dev/null @@ -1,388 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; -using Timeline.Controllers; -using Timeline.Filters; -using Timeline.Models; -using Timeline.Models.Http; -using Timeline.Models.Validation; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Controllers -{ - public class PersonalTimelineControllerTest : IDisposable - { - private readonly Mock _service; - - private readonly PersonalTimelineController _controller; - - public PersonalTimelineControllerTest() - { - _service = new Mock(); - _controller = new PersonalTimelineController(NullLogger.Instance, _service.Object); - } - - public void Dispose() - { - _controller.Dispose(); - } - - [Fact] - public void AttributeTest() - { - static void AssertUsernameParameter(MethodInfo m) - { - m.GetParameter("username") - .Should().BeDecoratedWith() - .And.BeDecoratedWith(); - } - - static void AssertBodyParamter(MethodInfo m) - { - var p = m.GetParameter("body"); - p.Should().BeDecoratedWith(); - p.ParameterType.Should().Be(typeof(TBody)); - } - - var type = typeof(PersonalTimelineController); - type.Should().BeDecoratedWith(); - - { - var m = type.GetMethod(nameof(PersonalTimelineController.TimelineGet)); - m.Should().BeDecoratedWith() - .And.BeDecoratedWith(); - AssertUsernameParameter(m); - } - - { - var m = type.GetMethod(nameof(PersonalTimelineController.PostListGet)); - m.Should().BeDecoratedWith() - .And.BeDecoratedWith(); - AssertUsernameParameter(m); - } - - { - var m = type.GetMethod(nameof(PersonalTimelineController.PostOperationCreate)); - m.Should().BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith(); - AssertUsernameParameter(m); - AssertBodyParamter(m); - } - - { - var m = type.GetMethod(nameof(PersonalTimelineController.PostOperationDelete)); - m.Should().BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith(); - AssertUsernameParameter(m); - AssertBodyParamter(m); - } - - { - var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeProperty)); - m.Should().BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith(); - AssertUsernameParameter(m); - AssertBodyParamter(m); - } - - { - var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeMember)); - m.Should().BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith(); - AssertUsernameParameter(m); - AssertBodyParamter(m); - } - } - - const string authUsername = "authuser"; - private void SetUser(bool administrator) - { - _controller.ControllerContext = new ControllerContext - { - HttpContext = new DefaultHttpContext - { - User = PrincipalHelper.Create(authUsername, administrator) - } - }; - } - - [Fact] - public async Task TimelineGet() - { - const string username = "username"; - var timelineInfo = new BaseTimelineInfo(); - _service.Setup(s => s.GetTimeline(username)).ReturnsAsync(timelineInfo); - (await _controller.TimelineGet(username)).Value.Should().Be(timelineInfo); - _service.VerifyAll(); - } - - [Fact] - public async Task PostListGet_Forbid() - { - const string username = "username"; - SetUser(false); - _service.Setup(s => s.HasReadPermission(username, authUsername)).ReturnsAsync(false); - var result = (await _controller.PostListGet(username)).Result - .Should().BeAssignableTo() - .Which; - result.StatusCode.Should().Be(StatusCodes.Status403Forbidden); - result.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.Common.Forbid); - _service.VerifyAll(); - } - - [Fact] - public async Task PostListGet_Admin_Success() - { - const string username = "username"; - SetUser(true); - _service.Setup(s => s.GetPosts(username)).ReturnsAsync(new List()); - (await _controller.PostListGet(username)).Value - .Should().BeAssignableTo>() - .Which.Should().NotBeNull().And.BeEmpty(); - _service.VerifyAll(); - } - - [Fact] - public async Task PostListGet_User_Success() - { - const string username = "username"; - SetUser(false); - _service.Setup(s => s.HasReadPermission(username, authUsername)).ReturnsAsync(true); - _service.Setup(s => s.GetPosts(username)).ReturnsAsync(new List()); - (await _controller.PostListGet(username)).Value - .Should().BeAssignableTo>() - .Which.Should().NotBeNull().And.BeEmpty(); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationCreate_Forbid() - { - const string username = "username"; - const string content = "cccc"; - SetUser(false); - _service.Setup(s => s.IsMemberOf(username, authUsername)).ReturnsAsync(false); - var result = (await _controller.PostOperationCreate(username, new TimelinePostCreateRequest - { - Content = content, - Time = null - })).Result.Should().NotBeNull().And.BeAssignableTo().Which; - result.StatusCode.Should().Be(StatusCodes.Status403Forbidden); - result.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.Common.Forbid); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationCreate_Admin_Success() - { - const string username = "username"; - const string content = "cccc"; - var response = new TimelinePostCreateResponse - { - Id = 3, - Time = DateTime.Now - }; - SetUser(true); - _service.Setup(s => s.CreatePost(username, authUsername, content, null)).ReturnsAsync(response); - var resultValue = (await _controller.PostOperationCreate(username, new TimelinePostCreateRequest - { - Content = content, - Time = null - })).Value; - resultValue.Should().NotBeNull() - .And.BeAssignableTo() - .And.BeEquivalentTo(response); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationCreate_User_Success() - { - const string username = "username"; - const string content = "cccc"; - var response = new TimelinePostCreateResponse - { - Id = 3, - Time = DateTime.Now - }; - SetUser(false); - _service.Setup(s => s.IsMemberOf(username, authUsername)).ReturnsAsync(true); - _service.Setup(s => s.CreatePost(username, authUsername, content, null)).ReturnsAsync(response); - var resultValue = (await _controller.PostOperationCreate(username, new TimelinePostCreateRequest - { - Content = content, - Time = null - })).Value; - resultValue.Should().NotBeNull() - .And.BeAssignableTo() - .And.BeEquivalentTo(response); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationDelete_Forbid() - { - const string username = "username"; - const long postId = 2; - SetUser(false); - _service.Setup(s => s.HasPostModifyPermission(username, postId, authUsername)).ReturnsAsync(false); - var result = (await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest - { - Id = postId - })).Should().NotBeNull().And.BeAssignableTo().Which; - result.StatusCode.Should().Be(StatusCodes.Status403Forbidden); - result.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.Common.Forbid); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationDelete_NotExist() - { - const string username = "username"; - const long postId = 2; - SetUser(true); - _service.Setup(s => s.DeletePost(username, postId)).ThrowsAsync(new TimelinePostNotExistException()); - var result = (await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest - { - Id = postId - })).Should().NotBeNull().And.BeAssignableTo().Which; - result.StatusCode.Should().Be(StatusCodes.Status400BadRequest); - result.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.TimelineController.PostOperationDelete_NotExist); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationDelete_Admin_Success() - { - const string username = "username"; - const long postId = 2; - SetUser(true); - _service.Setup(s => s.DeletePost(username, postId)).Returns(Task.CompletedTask); - var result = await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest - { - Id = postId - }); - result.Should().NotBeNull().And.BeAssignableTo(); - _service.VerifyAll(); - } - - [Fact] - public async Task PostOperationDelete_User_Success() - { - const string username = "username"; - const long postId = 2; - SetUser(false); - _service.Setup(s => s.DeletePost(username, postId)).Returns(Task.CompletedTask); - _service.Setup(s => s.HasPostModifyPermission(username, postId, authUsername)).ReturnsAsync(true); - var result = await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest - { - Id = postId - }); - result.Should().NotBeNull().And.BeAssignableTo(); - _service.VerifyAll(); - } - - [Fact] - public async Task TimelineChangeProperty_Success() - { - const string username = "username"; - var req = new TimelinePropertyChangeRequest - { - Description = "", - Visibility = TimelineVisibility.Private - }; - _service.Setup(s => s.ChangeProperty(username, req)).Returns(Task.CompletedTask); - var result = await _controller.TimelineChangeProperty(username, req); - result.Should().NotBeNull().And.BeAssignableTo(); - _service.VerifyAll(); - } - - [Fact] - public async Task TimelineChangeMember_Success() - { - const string username = "username"; - var add = new List { "aaa" }; - var remove = new List { "rrr" }; - _service.Setup(s => s.ChangeMember(username, add, remove)).Returns(Task.CompletedTask); - var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest - { - Add = add, - Remove = remove - }); - result.Should().NotBeNull().And.BeAssignableTo(); - _service.VerifyAll(); - } - - [Fact] - public async Task TimelineChangeMember_UsernameBadFormat() - { - const string username = "username"; - var add = new List { "aaa" }; - var remove = new List { "rrr" }; - _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync( - new TimelineMemberOperationUserException("test", new UsernameBadFormatException())); - var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest - { - Add = add, - Remove = remove - }); - result.Should().NotBeNull().And.BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.Common.InvalidModel); - _service.VerifyAll(); - } - - [Fact] - public async Task TimelineChangeMember_AddNotExist() - { - const string username = "username"; - var add = new List { "aaa" }; - var remove = new List { "rrr" }; - _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync( - new TimelineMemberOperationUserException("test", new UserNotExistException())); - var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest - { - Add = add, - Remove = remove - }); - result.Should().NotBeNull().And.BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); - _service.VerifyAll(); - } - - [Fact] - public async Task TimelineChangeMember_UnknownTimelineMemberOperationUserException() - { - const string username = "username"; - var add = new List { "aaa" }; - var remove = new List { "rrr" }; - _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync( - new TimelineMemberOperationUserException("test", null)); - await _controller.Awaiting(c => c.TimelineChangeMember(username, new TimelineMemberChangeRequest - { - Add = add, - Remove = remove - })).Should().ThrowAsync(); // Should rethrow. - } - } -} diff --git a/Timeline.Tests/Controllers/TokenControllerTest.cs b/Timeline.Tests/Controllers/TokenControllerTest.cs deleted file mode 100644 index 43e1a413..00000000 --- a/Timeline.Tests/Controllers/TokenControllerTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Timeline.Controllers; -using Timeline.Models; -using Timeline.Models.Http; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Controllers -{ - public class TokenControllerTest : IDisposable - { - private readonly Mock _mockUserService = new Mock(); - private readonly TestClock _mockClock = new TestClock(); - - - private readonly TokenController _controller; - - public TokenControllerTest() - { - _controller = new TokenController(_mockUserService.Object, NullLogger.Instance, _mockClock); - } - - public void Dispose() - { - _controller.Dispose(); - } - - [Theory] - [InlineData(null)] - [InlineData(100)] - public async Task Create_Ok(int? expire) - { - var mockCurrentTime = DateTime.Now; - _mockClock.MockCurrentTime = mockCurrentTime; - var mockCreateResult = new UserTokenCreateResult - { - Token = "mocktokenaaaaa", - User = new Models.User - { - Id = 1, - Username = MockUser.User.Username, - Administrator = MockUser.User.Administrator, - Version = 1 - } - }; - _mockUserService.Setup(s => s.CreateToken("u", "p", expire == null ? null : (DateTime?)mockCurrentTime.AddDays(expire.Value))).ReturnsAsync(mockCreateResult); - var action = await _controller.Create(new CreateTokenRequest - { - Username = "u", - Password = "p", - Expire = expire - }); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeEquivalentTo(new CreateTokenResponse - { - Token = mockCreateResult.Token, - User = MockUser.User.Info - }); - } - - [Fact] - public async Task Create_UserNotExist() - { - _mockUserService.Setup(s => s.CreateToken("u", "p", null)).ThrowsAsync(new UserNotExistException("u")); - var action = await _controller.Create(new CreateTokenRequest - { - Username = "u", - Password = "p", - Expire = null - }); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.TokenController.Create_BadCredential); - } - - [Fact] - public async Task Create_BadPassword() - { - _mockUserService.Setup(s => s.CreateToken("u", "p", null)).ThrowsAsync(new BadPasswordException("u")); - var action = await _controller.Create(new CreateTokenRequest - { - Username = "u", - Password = "p", - Expire = null - }); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.TokenController.Create_BadCredential); - } - - [Fact] - public async Task Verify_Ok() - { - const string token = "aaaaaaaaaaaaaa"; - _mockUserService.Setup(s => s.VerifyToken(token)).ReturnsAsync(new Models.User - { - Id = 1, - Username = MockUser.User.Username, - Administrator = MockUser.User.Administrator, - Version = 1 - }); - var action = await _controller.Verify(new VerifyTokenRequest { Token = token }); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.User.Should().BeEquivalentTo(MockUser.User.Info); - } - - public static IEnumerable Verify_BadRequest_Data() - { - yield return new object[] { new UserTokenTimeExpireException(), ErrorCodes.TokenController.Verify_TimeExpired }; - yield return new object[] { new UserTokenBadVersionException(), ErrorCodes.TokenController.Verify_OldVersion }; - yield return new object[] { new UserTokenBadFormatException(), ErrorCodes.TokenController.Verify_BadFormat }; - yield return new object[] { new UserNotExistException(), ErrorCodes.TokenController.Verify_UserNotExist }; - } - - [Theory] - [MemberData(nameof(Verify_BadRequest_Data))] - public async Task Verify_BadRequest(Exception e, int code) - { - const string token = "aaaaaaaaaaaaaa"; - _mockUserService.Setup(s => s.VerifyToken(token)).ThrowsAsync(e); - var action = await _controller.Verify(new VerifyTokenRequest { Token = token }); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(code); - } - } -} diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs deleted file mode 100644 index 3890712a..00000000 --- a/Timeline.Tests/Controllers/UserControllerTest.cs +++ /dev/null @@ -1,228 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Timeline.Controllers; -using Timeline.Models; -using Timeline.Models.Http; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Controllers -{ - public class UserControllerTest : IDisposable - { - private readonly Mock _mockUserService = new Mock(); - - private readonly UserController _controller; - - public UserControllerTest() - { - _controller = new UserController(NullLogger.Instance, _mockUserService.Object); - } - - public void Dispose() - { - _controller.Dispose(); - } - - [Fact] - public async Task GetList_Success() - { - var mockUserList = new Models.User[] { - new Models.User { Id = 1, Username = "aaa", Administrator = true, Version = 1 }, - new Models.User { Id = 2, Username = "bbb", Administrator = false, Version = 1 } - }; - _mockUserService.Setup(s => s.GetUsers()).ReturnsAsync(mockUserList); - var action = await _controller.List(); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeEquivalentTo( - mockUserList.Select(u => new User { Username = u.Username, Administrator = u.Administrator }).ToArray()); - } - - [Fact] - public async Task Get_Success() - { - const string username = "aaa"; - _mockUserService.Setup(s => s.GetUserByUsername(username)).ReturnsAsync(new Models.User - { - Id = 1, - Username = MockUser.User.Username, - Administrator = MockUser.User.Administrator, - Version = 1 - }); - var action = await _controller.Get(username); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeEquivalentTo(MockUser.User.Info); - } - - [Fact] - public async Task Get_NotFound() - { - const string username = "aaa"; - _mockUserService.Setup(s => s.GetUserByUsername(username)).ThrowsAsync(new UserNotExistException()); - var action = await _controller.Get(username); - action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); - } - - [Theory] - [InlineData(PutResult.Create, true)] - [InlineData(PutResult.Modify, false)] - public async Task Put_Success(PutResult result, bool create) - { - const string username = "aaa"; - const string password = "ppp"; - const bool administrator = true; - _mockUserService.Setup(s => s.PutUser(username, password, administrator)).ReturnsAsync(result); - var action = await _controller.Put(new UserPutRequest - { - Password = password, - Administrator = administrator - }, username); - var response = action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which; - response.Code.Should().Be(0); - response.Data.Create.Should().Be(create); - } - - [Fact] - public async Task Patch_Success() - { - const string username = "aaa"; - const string password = "ppp"; - const bool administrator = true; - _mockUserService.Setup(s => s.PatchUser(username, password, administrator)).Returns(Task.CompletedTask); - var action = await _controller.Patch(new UserPatchRequest - { - Password = password, - Administrator = administrator - }, username); - action.Should().BeAssignableTo(); - } - - [Fact] - public async Task Patch_NotExist() - { - const string username = "aaa"; - const string password = "ppp"; - const bool administrator = true; - _mockUserService.Setup(s => s.PatchUser(username, password, administrator)).ThrowsAsync(new UserNotExistException()); - var action = await _controller.Patch(new UserPatchRequest - { - Password = password, - Administrator = administrator - }, username); - action.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); - } - - [Fact] - public async Task Delete_Delete() - { - const string username = "aaa"; - _mockUserService.Setup(s => s.DeleteUser(username)).Returns(Task.CompletedTask); - var action = await _controller.Delete(username); - var body = action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which; - body.Code.Should().Be(0); - body.Data.Delete.Should().BeTrue(); - } - - [Fact] - public async Task Delete_NotExist() - { - const string username = "aaa"; - _mockUserService.Setup(s => s.DeleteUser(username)).ThrowsAsync(new UserNotExistException()); - var action = await _controller.Delete(username); - var body = action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which; - body.Code.Should().Be(0); - body.Data.Delete.Should().BeFalse(); - } - - [Fact] - public async Task Op_ChangeUsername_Success() - { - const string oldUsername = "aaa"; - const string newUsername = "bbb"; - _mockUserService.Setup(s => s.ChangeUsername(oldUsername, newUsername)).Returns(Task.CompletedTask); - var action = await _controller.ChangeUsername(new ChangeUsernameRequest { OldUsername = oldUsername, NewUsername = newUsername }); - action.Should().BeAssignableTo(); - } - - [Theory] - [InlineData(typeof(UserNotExistException), ErrorCodes.UserCommon.NotExist)] - [InlineData(typeof(ConfictException), ErrorCodes.UserController.ChangeUsername_Conflict)] - public async Task Op_ChangeUsername_Failure(Type exceptionType, int code) - { - const string oldUsername = "aaa"; - const string newUsername = "bbb"; - _mockUserService.Setup(s => s.ChangeUsername(oldUsername, newUsername)).ThrowsAsync(Activator.CreateInstance(exceptionType) as Exception); - var action = await _controller.ChangeUsername(new ChangeUsernameRequest { OldUsername = oldUsername, NewUsername = newUsername }); - action.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(code); - } - - [Fact] - public async Task Op_ChangePassword_Success() - { - const string username = "aaa"; - const string oldPassword = "aaa"; - const string newPassword = "bbb"; - _mockUserService.Setup(s => s.ChangePassword(username, oldPassword, newPassword)).Returns(Task.CompletedTask); - - _controller.ControllerContext = new ControllerContext() - { - HttpContext = new DefaultHttpContext() - { - User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] - { - new Claim(ClaimTypes.Name, username) - }, "TestAuthType")) - } - }; - - var action = await _controller.ChangePassword(new ChangePasswordRequest { OldPassword = oldPassword, NewPassword = newPassword }); - action.Should().BeAssignableTo(); - } - - [Fact] - public async Task Op_ChangePassword_BadPassword() - { - const string username = "aaa"; - const string oldPassword = "aaa"; - const string newPassword = "bbb"; - _mockUserService.Setup(s => s.ChangePassword(username, oldPassword, newPassword)).ThrowsAsync(new BadPasswordException()); - - _controller.ControllerContext = new ControllerContext() - { - HttpContext = new DefaultHttpContext() - { - User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] - { - new Claim(ClaimTypes.Name, username) - }, "TestAuthType")) - } - }; - - var action = await _controller.ChangePassword(new ChangePasswordRequest { OldPassword = oldPassword, NewPassword = newPassword }); - action.Should().BeAssignableTo() - .Which.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.UserController.ChangePassword_BadOldPassword); - } - } -} diff --git a/Timeline.Tests/Controllers/UserDetailControllerTest.cs b/Timeline.Tests/Controllers/UserDetailControllerTest.cs deleted file mode 100644 index ffd88790..00000000 --- a/Timeline.Tests/Controllers/UserDetailControllerTest.cs +++ /dev/null @@ -1,98 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Moq; -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Timeline.Controllers; -using Timeline.Filters; -using Timeline.Models.Validation; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Controllers -{ - public class UserDetailControllerTest : IDisposable - { - private readonly Mock _mockUserDetailService; - private readonly UserDetailController _controller; - - public UserDetailControllerTest() - { - _mockUserDetailService = new Mock(); - _controller = new UserDetailController(_mockUserDetailService.Object); - } - - public void Dispose() - { - _controller.Dispose(); - } - - [Fact] - public void AttributeTest() - { - typeof(UserDetailController).Should().BeDecoratedWith(); - - var getNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.GetNickname)); - getNickname.Should().BeDecoratedWith() - .And.BeDecoratedWith(); - getNickname.GetParameter("username").Should().BeDecoratedWith() - .And.BeDecoratedWith(); - - var putNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.PutNickname)); - putNickname.Should().BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith(); - putNickname.GetParameter("username").Should().BeDecoratedWith() - .And.BeDecoratedWith(); - var stringLengthAttributeOnPutBody = putNickname.GetParameter("body").Should().BeDecoratedWith() - .And.BeDecoratedWith() - .Which; - stringLengthAttributeOnPutBody.MinimumLength.Should().Be(1); - stringLengthAttributeOnPutBody.MaximumLength.Should().Be(10); - - var deleteNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.DeleteNickname)); - deleteNickname.Should().BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith() - .And.BeDecoratedWith(); - deleteNickname.GetParameter("username").Should().BeDecoratedWith() - .And.BeDecoratedWith(); - } - - [Fact] - public async Task GetNickname_ShouldWork() - { - const string username = "uuu"; - const string nickname = "nnn"; - _mockUserDetailService.Setup(s => s.GetNickname(username)).ReturnsAsync(nickname); - var actionResult = await _controller.GetNickname(username); - actionResult.Result.Should().BeAssignableTo(nickname); - _mockUserDetailService.VerifyAll(); - } - - [Fact] - public async Task PutNickname_ShouldWork() - { - const string username = "uuu"; - const string nickname = "nnn"; - _mockUserDetailService.Setup(s => s.SetNickname(username, nickname)).Returns(Task.CompletedTask); - var actionResult = await _controller.PutNickname(username, nickname); - actionResult.Should().BeAssignableTo(); - _mockUserDetailService.VerifyAll(); - } - - [Fact] - public async Task DeleteNickname_ShouldWork() - { - const string username = "uuu"; - _mockUserDetailService.Setup(s => s.SetNickname(username, null)).Returns(Task.CompletedTask); - var actionResult = await _controller.DeleteNickname(username); - actionResult.Should().BeAssignableTo(); - _mockUserDetailService.VerifyAll(); - } - } -} diff --git a/Timeline.Tests/DatabaseTest.cs b/Timeline.Tests/DatabaseTest.cs deleted file mode 100644 index a15823a9..00000000 --- a/Timeline.Tests/DatabaseTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -using FluentAssertions; -using System; -using System.Linq; -using Timeline.Entities; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests -{ - public class DatabaseTest : IDisposable - { - private readonly TestDatabase _database; - private readonly DatabaseContext _context; - - public DatabaseTest() - { - _database = new TestDatabase(); - _context = _database.Context; - } - - public void Dispose() - { - _database.Dispose(); - } - - [Fact] - public void DeleteUserShouldAlsoDeleteAvatar() - { - var user = _context.Users.First(); - _context.UserAvatars.Count().Should().Be(0); - _context.UserAvatars.Add(new UserAvatarEntity - { - Data = null, - Type = null, - ETag = null, - LastModified = DateTime.Now, - UserId = user.Id - }); - _context.SaveChanges(); - _context.UserAvatars.Count().Should().Be(1); - _context.Users.Remove(user); - _context.SaveChanges(); - _context.UserAvatars.Count().Should().Be(0); - } - - [Fact] - public void DeleteUserShouldAlsoDeleteDetail() - { - var user = _context.Users.First(); - _context.UserDetails.Count().Should().Be(0); - _context.UserDetails.Add(new UserDetailEntity - { - Nickname = null, - UserId = user.Id - }); - _context.SaveChanges(); - _context.UserDetails.Count().Should().Be(1); - _context.Users.Remove(user); - _context.SaveChanges(); - _context.UserDetails.Count().Should().Be(0); - } - } -} diff --git a/Timeline.Tests/GlobalSuppressions.cs b/Timeline.Tests/GlobalSuppressions.cs index 1d1d294b..0f873033 100644 --- a/Timeline.Tests/GlobalSuppressions.cs +++ b/Timeline.Tests/GlobalSuppressions.cs @@ -12,3 +12,5 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Test classes do not need to implement it that way.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2234:Pass system uri objects instead of strings", Justification = "I really don't understand this rule.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Tests do not need make strings resources.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1054:Uri parameters should not be strings", Justification = "That's unnecessary.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "That's unnecessary.")] diff --git a/Timeline.Tests/Helpers/MockUser.cs b/Timeline.Tests/Helpers/MockUser.cs deleted file mode 100644 index 49576842..00000000 --- a/Timeline.Tests/Helpers/MockUser.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Timeline.Models.Http; - -namespace Timeline.Tests.Helpers -{ - public class MockUser - { - public MockUser(string username, string password, bool administrator) - { - Info = new User { Username = username, Administrator = administrator }; - Password = password; - } - - public User Info { get; set; } - public string Username => Info.Username; - public string Password { get; set; } - public bool Administrator => Info.Administrator; - - public static MockUser User { get; } = new MockUser("user", "userpassword", false); - public static MockUser Admin { get; } = new MockUser("admin", "adminpassword", true); - - public static IReadOnlyList UserInfoList { get; } = new List { User.Info, Admin.Info }; - } -} diff --git a/Timeline.Tests/Helpers/PrincipalHelper.cs b/Timeline.Tests/Helpers/PrincipalHelper.cs deleted file mode 100644 index 89f3f7b1..00000000 --- a/Timeline.Tests/Helpers/PrincipalHelper.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; -using System.Security.Claims; -using Timeline.Models; - -namespace Timeline.Tests.Helpers -{ - public static class PrincipalHelper - { - internal const string AuthScheme = "TESTAUTH"; - - internal static ClaimsPrincipal Create(string username, bool administrator) - { - var identity = new ClaimsIdentity(AuthScheme); - identity.AddClaim(new Claim(identity.NameClaimType, username, ClaimValueTypes.String)); - identity.AddClaims(UserRoleConvert.ToArray(administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String))); - - var principal = new ClaimsPrincipal(); - principal.AddIdentity(identity); - - return principal; - } - } -} diff --git a/Timeline.Tests/Helpers/ResponseAssertions.cs b/Timeline.Tests/Helpers/ResponseAssertions.cs index be0043dc..f01a0677 100644 --- a/Timeline.Tests/Helpers/ResponseAssertions.cs +++ b/Timeline.Tests/Helpers/ResponseAssertions.cs @@ -125,6 +125,13 @@ namespace Timeline.Tests.Helpers return assertions.HaveJsonBody(because, becauseArgs); } + public static void HaveCommonBody(this HttpResponseMessageAssertions assertions, int code, string message = null, params object[] messageArgs) + { + message = string.IsNullOrEmpty(message) ? "" : ", " + string.Format(CultureInfo.CurrentCulture, message, messageArgs); + var body = assertions.HaveCommonBody("Response body should be CommonResponse{0}", message).Which; + body.Code.Should().Be(code, "Response body code is not the specified one{0}", message); + } + public static AndWhichConstraint> HaveCommonDataBody(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs) { return assertions.HaveJsonBody>(because, becauseArgs); @@ -148,13 +155,6 @@ namespace Timeline.Tests.Helpers body.Data.Delete.Should().Be(delete); } - public static void HaveCommonResponseBody(this HttpResponseMessageAssertions assertions, int code, string message = null, params object[] messageArgs) - { - message = string.IsNullOrEmpty(message) ? "" : ", " + string.Format(CultureInfo.CurrentCulture, message, messageArgs); - var body = assertions.HaveJsonBody("Response body should be CommonResponse{0}", message).Which; - body.Code.Should().Be(code, "Response body code is not the specified one{0}", message); - } - public static void BeInvalidModel(this HttpResponseMessageAssertions assertions, string message = null) { message = string.IsNullOrEmpty(message) ? "" : ", " + message; diff --git a/Timeline.Tests/Helpers/TestApplication.cs b/Timeline.Tests/Helpers/TestApplication.cs index d18f2848..14cafea3 100644 --- a/Timeline.Tests/Helpers/TestApplication.cs +++ b/Timeline.Tests/Helpers/TestApplication.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; @@ -8,19 +10,21 @@ namespace Timeline.Tests.Helpers { public class TestApplication : IDisposable { - public TestDatabase Database { get; } = new TestDatabase(); + public SqliteConnection DatabaseConnection { get; } + public WebApplicationFactory Factory { get; } public TestApplication(WebApplicationFactory factory) { + DatabaseConnection = new SqliteConnection("Data Source=:memory:;"); + Factory = factory.WithWebHostBuilder(builder => { - builder.ConfigureServices(services => + builder.ConfigureTestServices(services => { - services.AddEntityFrameworkSqlite(); services.AddDbContext(options => { - options.UseSqlite(Database.Connection); + options.UseSqlite(DatabaseConnection); }); }); }); @@ -28,7 +32,8 @@ namespace Timeline.Tests.Helpers public void Dispose() { - Database.Dispose(); + DatabaseConnection.Close(); + DatabaseConnection.Dispose(); } } } diff --git a/Timeline.Tests/Helpers/TestClock.cs b/Timeline.Tests/Helpers/TestClock.cs deleted file mode 100644 index 12b320d3..00000000 --- a/Timeline.Tests/Helpers/TestClock.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Timeline.Services; - -namespace Timeline.Tests.Helpers -{ - public class TestClock : IClock - { - public DateTime? MockCurrentTime { get; set; } = null; - - public DateTime GetCurrentTime() - { - return MockCurrentTime.GetValueOrDefault(DateTime.Now); - } - } -} diff --git a/Timeline.Tests/Helpers/TestDatabase.cs b/Timeline.Tests/Helpers/TestDatabase.cs deleted file mode 100644 index e29a71fa..00000000 --- a/Timeline.Tests/Helpers/TestDatabase.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using Timeline.Entities; -using Timeline.Models; -using Timeline.Services; - -namespace Timeline.Tests.Helpers -{ - public class TestDatabase : IDisposable - { - // currently password service is thread safe, so we share a static one. - private static PasswordService PasswordService { get; } = new PasswordService(); - - private static UserEntity CreateEntityFromMock(MockUser user) - { - return new UserEntity - { - Username = user.Username, - Password = PasswordService.HashPassword(user.Password), - Roles = UserRoleConvert.ToString(user.Administrator), - Avatar = null - }; - } - - private static IEnumerable CreateDefaultMockEntities() - { - // emmmmmmm. Never reuse the user instances because EF Core uses them, which will cause strange things. - yield return CreateEntityFromMock(MockUser.User); - yield return CreateEntityFromMock(MockUser.Admin); - } - - private static void InitDatabase(DatabaseContext context) - { - context.Database.EnsureCreated(); - context.Users.AddRange(CreateDefaultMockEntities()); - context.SaveChanges(); - } - - public SqliteConnection Connection { get; } - public DatabaseContext Context { get; } - - public TestDatabase() - { - Connection = new SqliteConnection("Data Source=:memory:;"); - Connection.Open(); - - var options = new DbContextOptionsBuilder() - .UseSqlite(Connection) - .Options; - - Context = new DevelopmentDatabaseContext(options); - - InitDatabase(Context); - } - - private List _extraMockUsers; - - public IReadOnlyList ExtraMockUsers => _extraMockUsers; - - public void CreateExtraMockUsers(int count) - { - if (count <= 0) - throw new ArgumentOutOfRangeException(nameof(count), count, "Additional user count must be bigger than 0."); - if (_extraMockUsers != null) - throw new InvalidOperationException("Already create mock users."); - - _extraMockUsers = new List(); - for (int i = 0; i < count; i++) - { - _extraMockUsers.Add(new MockUser($"user{i}", $"password", false)); - } - - Context.AddRange(_extraMockUsers.Select(u => CreateEntityFromMock(u))); - Context.SaveChanges(); - } - - public void Dispose() - { - Context.Dispose(); - - Connection.Close(); - Connection.Dispose(); - } - - } -} diff --git a/Timeline.Tests/Helpers/UseCultureAttribute.cs b/Timeline.Tests/Helpers/UseCultureAttribute.cs deleted file mode 100644 index 017d77a8..00000000 --- a/Timeline.Tests/Helpers/UseCultureAttribute.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Globalization; -using System.Reflection; -using System.Threading; -using Xunit.Sdk; - - -namespace Timeline.Tests.Helpers -{ - // Copied from https://github.com/xunit/samples.xunit/blob/master/UseCulture/UseCultureAttribute.cs - - /// - /// Apply this attribute to your test method to replace the - /// and - /// with another culture. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class UseCultureAttribute : BeforeAfterTestAttribute - { - readonly Lazy culture; - readonly Lazy uiCulture; - - CultureInfo originalCulture; - CultureInfo originalUICulture; - - /// - /// Replaces the culture and UI culture of the current thread with - /// - /// - /// The name of the culture. - /// - /// - /// This constructor overload uses for both - /// and . - /// - /// - public UseCultureAttribute(string culture) - : this(culture, culture) { } - - /// - /// Replaces the culture and UI culture of the current thread with - /// and - /// - /// The name of the culture. - /// The name of the UI culture. - public UseCultureAttribute(string culture, string uiCulture) - { - this.culture = new Lazy(() => new CultureInfo(culture, false)); - this.uiCulture = new Lazy(() => new CultureInfo(uiCulture, false)); - } - - /// - /// Gets the culture. - /// - public CultureInfo Culture { get { return culture.Value; } } - - /// - /// Gets the UI culture. - /// - public CultureInfo UICulture { get { return uiCulture.Value; } } - - /// - /// Stores the current - /// and - /// and replaces them with the new cultures defined in the constructor. - /// - /// The method under test - public override void Before(MethodInfo methodUnderTest) - { - originalCulture = Thread.CurrentThread.CurrentCulture; - originalUICulture = Thread.CurrentThread.CurrentUICulture; - - Thread.CurrentThread.CurrentCulture = Culture; - Thread.CurrentThread.CurrentUICulture = UICulture; - - CultureInfo.CurrentCulture.ClearCachedData(); - CultureInfo.CurrentUICulture.ClearCachedData(); - } - - /// - /// Restores the original and - /// to - /// - /// The method under test - public override void After(MethodInfo methodUnderTest) - { - Thread.CurrentThread.CurrentCulture = originalCulture; - Thread.CurrentThread.CurrentUICulture = originalUICulture; - - CultureInfo.CurrentCulture.ClearCachedData(); - CultureInfo.CurrentUICulture.ClearCachedData(); - } - } -} diff --git a/Timeline.Tests/IntegratedTests/AuthorizationTest.cs b/Timeline.Tests/IntegratedTests/AuthorizationTest.cs index 0bc094af..4aa6b3ae 100644 --- a/Timeline.Tests/IntegratedTests/AuthorizationTest.cs +++ b/Timeline.Tests/IntegratedTests/AuthorizationTest.cs @@ -22,7 +22,7 @@ namespace Timeline.Tests.IntegratedTests [Fact] public async Task UnauthenticationTest() { - using var client = await CreateClientWithNoAuth(); + using var client = await CreateDefaultClient(); var response = await client.GetAsync(AuthorizeUrl); response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } @@ -48,7 +48,7 @@ namespace Timeline.Tests.IntegratedTests [Fact] public async Task AdminAuthorizationTest() { - using var client = await CreateClientAsAdmin(); + using var client = await CreateClientAsAdministrator(); var response1 = await client.GetAsync(UserUrl); response1.Should().HaveStatusCode(HttpStatusCode.OK); var response2 = await client.GetAsync(AdminUrl); diff --git a/Timeline.Tests/IntegratedTests/I18nTest.cs b/Timeline.Tests/IntegratedTests/I18nTest.cs deleted file mode 100644 index 855179af..00000000 --- a/Timeline.Tests/IntegratedTests/I18nTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Mvc.Testing; -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.IntegratedTests -{ - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1054:Uri parameters should not be strings")] - public class I18nTest : IntegratedTestBase - { - private readonly HttpClient _client; - - public I18nTest(WebApplicationFactory factory) - : base(factory) - { - _client = Factory.CreateDefaultClient(); - } - - protected override void OnDispose() - { - _client.Dispose(); - } - - private const string DirectUrl = "testing/i18n/direct"; - private const string LocalizerUrl = "testing/i18n/localizer"; - - [Theory] - [InlineData(DirectUrl)] - [InlineData(LocalizerUrl)] - public async Task DefaultShouldReturnEnglish(string url) - { - (await _client.GetStringAsync(url)).Should().ContainEquivalentOf("English"); - } - - [Theory] - [InlineData(DirectUrl, "en", true)] - [InlineData(LocalizerUrl, "en", true)] - [InlineData(DirectUrl, "en-US", true)] - [InlineData(LocalizerUrl, "en-US", true)] - [InlineData(DirectUrl, "zh", false)] - [InlineData(LocalizerUrl, "zh", false)] - public async Task ShouldWork(string url, string acceptLanguage, bool english) - { - var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri(url, UriKind.RelativeOrAbsolute) - }; - request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(acceptLanguage)); - var body = await (await _client.SendAsync(request)).Content.ReadAsStringAsync(); - body.Should().ContainEquivalentOf(english ? "English" : "中文"); - request.Dispose(); - } - } -} diff --git a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 242a452d..721a25af 100644 --- a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -1,36 +1,17 @@ -using Microsoft.AspNetCore.Mvc.Testing; +using AutoMapper; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Timeline.Models.Http; +using Timeline.Services; using Timeline.Tests.Helpers; using Xunit; namespace Timeline.Tests.IntegratedTests { - public enum AuthType - { - None, - User, - Admin - } - - public static class AuthTypeExtensions - { - public static MockUser GetMockUser(this AuthType authType) - { - return authType switch - { - AuthType.None => null, - AuthType.User => MockUser.User, - AuthType.Admin => MockUser.Admin, - _ => throw new InvalidOperationException("Unknown auth type.") - }; - } - - public static string GetUsername(this AuthType authType) => authType.GetMockUser().Username; - } public abstract class IntegratedTestBase : IClassFixture>, IDisposable { @@ -38,14 +19,62 @@ namespace Timeline.Tests.IntegratedTests protected WebApplicationFactory Factory => TestApp.Factory; - public IntegratedTestBase(WebApplicationFactory factory) + public IntegratedTestBase(WebApplicationFactory factory) : this(factory, 1) + { + + } + + public IntegratedTestBase(WebApplicationFactory factory, int userCount) { + if (userCount < 0) + throw new ArgumentOutOfRangeException(nameof(userCount), userCount, "User count can't be negative."); + TestApp = new TestApplication(factory); + + using (var scope = Factory.Services.CreateScope()) + { + var users = new List() + { + new User + { + Username = "admin", + Password = "adminpw", + Administrator = true, + Nickname = "administrator" + } + }; + + for (int i = 1; i <= userCount; i++) + { + users.Add(new User + { + Username = $"user{i}", + Password = $"user{i}pw", + Administrator = false, + Nickname = $"imuser{i}" + }); + } + + var userInfoList = new List(); + var userInfoForAdminList = new List(); + + var userService = scope.ServiceProvider.GetRequiredService(); + var mapper = scope.ServiceProvider.GetRequiredService(); + + foreach (var user in users) + { + userService.CreateUser(user); + userInfoList.Add(mapper.Map(user)); + userInfoForAdminList.Add(mapper.Map(user)); + } + + UserInfoList = userInfoList; + UserInfoForAdminList = userInfoForAdminList; + } } protected virtual void OnDispose() { - } public void Dispose() @@ -54,14 +83,11 @@ namespace Timeline.Tests.IntegratedTests TestApp.Dispose(); } - protected void CreateExtraMockUsers(int count) - { - TestApp.Database.CreateExtraMockUsers(count); - } + public IReadOnlyList UserInfoList { get; } - protected IReadOnlyList ExtraMockUsers => TestApp.Database.ExtraMockUsers; + public IReadOnlyList UserInfoForAdminList { get; } - public Task CreateClientWithNoAuth() + public Task CreateDefaultClient() { return Task.FromResult(Factory.CreateDefaultClient()); } @@ -77,18 +103,25 @@ namespace Timeline.Tests.IntegratedTests return client; } - public Task CreateClientAs(MockUser user) + public Task CreateClientAs(int userNumber) { - if (user == null) - return CreateClientWithNoAuth(); - return CreateClientWithCredential(user.Username, user.Password); - } - - public Task CreateClientAs(AuthType authType) => CreateClientAs(authType.GetMockUser()); - + if (userNumber < 0) + throw new ArgumentOutOfRangeException(nameof(userNumber), "User number can't be negative."); - public Task CreateClientAsUser() => CreateClientAs(MockUser.User); - public Task CreateClientAsAdmin() => CreateClientAs(MockUser.Admin); + if (userNumber == 0) + return CreateClientWithCredential("admin", "adminpw"); + else + return CreateClientWithCredential($"user{userNumber}", $"user{userNumber}pw"); + } + public Task CreateClientAsAdministrator() + { + return CreateClientAs(0); + } + + public Task CreateClientAsUser() + { + return CreateClientAs(1); + } } } diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs index 51e2d05e..14600659 100644 --- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Timeline.Models; using Timeline.Models.Http; using Timeline.Tests.Helpers; using Xunit; @@ -15,7 +14,7 @@ namespace Timeline.Tests.IntegratedTests public class PersonalTimelineTest : IntegratedTestBase { public PersonalTimelineTest(WebApplicationFactory factory) - : base(factory) + : base(factory, 3) { } @@ -23,11 +22,11 @@ namespace Timeline.Tests.IntegratedTests [Fact] public async Task TimelineGet_Should_Work() { - using var client = await CreateClientWithNoAuth(); - var res = await client.GetAsync("users/user/timeline"); + using var client = await CreateDefaultClient(); + var res = await client.GetAsync("users/user1/timeline"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; - body.Owner.Should().Be("user"); + body.Owner.Should().BeEquivalentTo(UserInfoList[1]); body.Visibility.Should().Be(TimelineVisibility.Register); body.Description.Should().Be(""); body.Members.Should().NotBeNull().And.BeEmpty(); @@ -40,7 +39,7 @@ namespace Timeline.Tests.IntegratedTests async Task AssertDescription(string description) { - var res = await client.GetAsync("users/user/timeline"); + var res = await client.GetAsync("users/user1/timeline"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Description.Should().Be(description); @@ -50,20 +49,20 @@ namespace Timeline.Tests.IntegratedTests await AssertDescription(""); { - var res = await client.PostAsJsonAsync("users/user/timeline/op/property", - new TimelinePropertyChangeRequest { Description = mockDescription }); + var res = await client.PatchAsJsonAsync("users/user1/timeline", + new TimelinePatchRequest { Description = mockDescription }); res.Should().HaveStatusCode(200); await AssertDescription(mockDescription); } { - var res = await client.PostAsJsonAsync("users/user/timeline/op/property", - new TimelinePropertyChangeRequest { Description = null }); + var res = await client.PatchAsJsonAsync("users/user1/timeline", + new TimelinePatchRequest { Description = null }); res.Should().HaveStatusCode(200); await AssertDescription(mockDescription); } { - var res = await client.PostAsJsonAsync("users/user/timeline/op/property", - new TimelinePropertyChangeRequest { Description = "" }); + var res = await client.PatchAsJsonAsync("users/user1/timeline", + new TimelinePatchRequest { Description = "" }); res.Should().HaveStatusCode(200); await AssertDescription(""); } @@ -141,13 +140,13 @@ namespace Timeline.Tests.IntegratedTests { var res = await client.PostAsJsonAsync("users/user/timeline/op/property", - new TimelinePropertyChangeRequest { Description = "hahaha" }); + new TimelinePatchRequest { Description = "hahaha" }); res.Should().HaveStatusCode(opPropertyUser); } { var res = await client.PostAsJsonAsync("users/admin/timeline/op/property", - new TimelinePropertyChangeRequest { Description = "hahaha" }); + new TimelinePatchRequest { Description = "hahaha" }); res.Should().HaveStatusCode(opPropertyAdmin); } @@ -193,7 +192,7 @@ namespace Timeline.Tests.IntegratedTests { using var client = await CreateClientAsUser(); var res = await client.PostAsJsonAsync("users/user/timeline/op/property", - new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Public }); + new TimelinePatchRequest { Visibility = TimelineVisibility.Public }); res.Should().HaveStatusCode(200); } { @@ -208,12 +207,12 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsAdmin(); { var res = await client.PostAsJsonAsync("users/user/timeline/op/property", - new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Private }); + new TimelinePatchRequest { Visibility = TimelineVisibility.Private }); res.Should().HaveStatusCode(200); } { var res = await client.PostAsJsonAsync("users/admin/timeline/op/property", - new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Private }); + new TimelinePatchRequest { Visibility = TimelineVisibility.Private }); res.Should().HaveStatusCode(200); } } @@ -331,7 +330,7 @@ namespace Timeline.Tests.IntegratedTests } { // self can delete self - var postId = await CreatePost(MockUser.User, "user"); + var postId = await CreatePost(MockUser.Ordinary, "user"); using var client = await CreateClientAsUser(); var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete", new TimelinePostDeleteRequest { Id = postId }); @@ -339,7 +338,7 @@ namespace Timeline.Tests.IntegratedTests } { // admin can delete any - var postId = await CreatePost(MockUser.User, "user"); + var postId = await CreatePost(MockUser.Ordinary, "user"); using var client = await CreateClientAsAdmin(); var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete", new TimelinePostDeleteRequest { Id = postId }); diff --git a/Timeline.Tests/IntegratedTests/TokenTest.cs b/Timeline.Tests/IntegratedTests/TokenTest.cs index ecd5d0b8..8ee19999 100644 --- a/Timeline.Tests/IntegratedTests/TokenTest.cs +++ b/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -53,7 +53,7 @@ namespace Timeline.Tests.IntegratedTests public static IEnumerable CreateToken_UserCredential_Data() { yield return new[] { "usernotexist", "p" }; - yield return new[] { MockUser.User.Username, "???" }; + yield return new[] { MockUser.Ordinary.Username, "???" }; } [Theory] @@ -73,11 +73,11 @@ namespace Timeline.Tests.IntegratedTests { using var client = await CreateClientWithNoAuth(); var response = await client.PostAsJsonAsync(CreateTokenUrl, - new CreateTokenRequest { Username = MockUser.User.Username, Password = MockUser.User.Password }); + new CreateTokenRequest { Username = MockUser.Ordinary.Username, Password = MockUser.Ordinary.Password }); var body = response.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Token.Should().NotBeNullOrWhiteSpace(); - body.User.Should().BeEquivalentTo(MockUser.User.Info); + body.User.Should().BeEquivalentTo(MockUser.Ordinary.Info); } [Fact] @@ -103,13 +103,13 @@ namespace Timeline.Tests.IntegratedTests public async Task VerifyToken_OldVersion() { using var client = await CreateClientWithNoAuth(); - var token = (await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password)).Token; + var token = (await CreateUserTokenAsync(client, MockUser.Ordinary.Username, MockUser.Ordinary.Password)).Token; using (var scope = Factory.Server.Host.Services.CreateScope()) // UserService is scoped. { // create a user for test var userService = scope.ServiceProvider.GetRequiredService(); - await userService.PatchUser(MockUser.User.Username, null, null); + await userService.PatchUser(MockUser.Ordinary.Username, null, null); } (await client.PostAsJsonAsync(VerifyTokenUrl, @@ -123,12 +123,12 @@ namespace Timeline.Tests.IntegratedTests public async Task VerifyToken_UserNotExist() { using var client = await CreateClientWithNoAuth(); - var token = (await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password)).Token; + var token = (await CreateUserTokenAsync(client, MockUser.Ordinary.Username, MockUser.Ordinary.Password)).Token; using (var scope = Factory.Server.Host.Services.CreateScope()) // UserService is scoped. { var userService = scope.ServiceProvider.GetRequiredService(); - await userService.DeleteUser(MockUser.User.Username); + await userService.DeleteUser(MockUser.Ordinary.Username); } (await client.PostAsJsonAsync(VerifyTokenUrl, @@ -160,12 +160,12 @@ namespace Timeline.Tests.IntegratedTests public async Task VerifyToken_Success() { using var client = await CreateClientWithNoAuth(); - var createTokenResult = await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password); + var createTokenResult = await CreateUserTokenAsync(client, MockUser.Ordinary.Username, MockUser.Ordinary.Password); var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = createTokenResult.Token }); response.Should().HaveStatusCode(200) .And.HaveJsonBody() - .Which.User.Should().BeEquivalentTo(MockUser.User.Info); + .Which.User.Should().BeEquivalentTo(MockUser.Ordinary.Info); } } } diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index a4e10634..989207e2 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -29,7 +29,6 @@ namespace Timeline.Tests.IntegratedTests } [Fact] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "HttpMessageRequest should be disposed ???")] public async Task Test() { Avatar mockAvatar = new Avatar @@ -61,7 +60,7 @@ namespace Timeline.Tests.IntegratedTests EntityTagHeaderValue eTag; { - var res = await client.GetAsync($"users/user/avatar"); + var res = await client.GetAsync($"users/user1/avatar"); res.Should().HaveStatusCode(200); res.Content.Headers.ContentType.MediaType.Should().Be("image/png"); var body = await res.Content.ReadAsByteArrayAsync(); @@ -78,7 +77,7 @@ namespace Timeline.Tests.IntegratedTests { var request = new HttpRequestMessage() { - RequestUri = new Uri(client.BaseAddress, "users/user/avatar"), + RequestUri = new Uri(client.BaseAddress, "users/user1/avatar"), Method = HttpMethod.Get, }; request.Headers.TryAddWithoutValidation("If-None-Match", "\"dsdfd"); @@ -90,7 +89,7 @@ namespace Timeline.Tests.IntegratedTests { var request = new HttpRequestMessage() { - RequestUri = new Uri(client.BaseAddress, "users/user/avatar"), + RequestUri = new Uri(client.BaseAddress, "users/user1/avatar"), Method = HttpMethod.Get, }; request.Headers.TryAddWithoutValidation("If-None-Match", "\"aaa\""); @@ -101,7 +100,7 @@ namespace Timeline.Tests.IntegratedTests { var request = new HttpRequestMessage() { - RequestUri = new Uri(client.BaseAddress, "users/user/avatar"), + RequestUri = new Uri(client.BaseAddress, "users/user1/avatar"), Method = HttpMethod.Get, }; request.Headers.Add("If-None-Match", eTag.ToString()); @@ -112,7 +111,7 @@ namespace Timeline.Tests.IntegratedTests { var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - var res = await client.PutAsync("users/user/avatar", content); + var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Header.ContentLength_Missing); ; } @@ -120,7 +119,7 @@ namespace Timeline.Tests.IntegratedTests { var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 1; - var res = await client.PutAsync("users/user/avatar", content); + var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Header.ContentType_Missing); } @@ -129,13 +128,13 @@ namespace Timeline.Tests.IntegratedTests var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 0; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - var res = await client.PutAsync("users/user/avatar", content); + var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Header.ContentLength_Zero); } { - var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/notaccept"); + var res = await client.PutByteArrayAsync("users/user1/avatar", new[] { (byte)0x00 }, "image/notaccept"); res.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); } @@ -143,7 +142,7 @@ namespace Timeline.Tests.IntegratedTests var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 1000 * 1000 * 11; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - var res = await client.PutAsync("users/user/avatar", content); + var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Content.TooBig); } @@ -152,7 +151,7 @@ namespace Timeline.Tests.IntegratedTests var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 2; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - var res = await client.PutAsync("users/user/avatar", content); + var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Content.UnmatchedLength_Smaller); } @@ -161,34 +160,34 @@ namespace Timeline.Tests.IntegratedTests var content = new ByteArrayContent(new[] { (byte)0x00, (byte)0x01 }); content.Headers.ContentLength = 1; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - var res = await client.PutAsync("users/user/avatar", content); + var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Content.UnmatchedLength_Bigger); } { - var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/png"); + var res = await client.PutByteArrayAsync("users/user1/avatar", new[] { (byte)0x00 }, "image/png"); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.UserAvatar.BadFormat_CantDecode); } { - var res = await client.PutByteArrayAsync("users/user/avatar", mockAvatar.Data, "image/jpeg"); + var res = await client.PutByteArrayAsync("users/user1/avatar", mockAvatar.Data, "image/jpeg"); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.UserAvatar.BadFormat_UnmatchedFormat); } { - var res = await client.PutByteArrayAsync("users/user/avatar", ImageHelper.CreatePngWithSize(100, 200), "image/png"); + var res = await client.PutByteArrayAsync("users/user1/avatar", ImageHelper.CreatePngWithSize(100, 200), "image/png"); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.UserAvatar.BadFormat_BadSize); } { - var res = await client.PutByteArrayAsync("users/user/avatar", mockAvatar.Data, mockAvatar.Type); + var res = await client.PutByteArrayAsync("users/user1/avatar", mockAvatar.Data, mockAvatar.Type); res.Should().HaveStatusCode(HttpStatusCode.OK); - var res2 = await client.GetAsync("users/user/avatar"); + var res2 = await client.GetAsync("users/user1/avatar"); res2.Should().HaveStatusCode(200); res2.Content.Headers.ContentType.MediaType.Should().Be(mockAvatar.Type); var body = await res2.Content.ReadAsByteArrayAsync(); @@ -204,7 +203,7 @@ namespace Timeline.Tests.IntegratedTests foreach ((var mimeType, var format) in formats) { - var res = await client.PutByteArrayAsync("users/user/avatar", ImageHelper.CreateImageWithSize(100, 100, format), mimeType); + var res = await client.PutByteArrayAsync("users/user1/avatar", ImageHelper.CreateImageWithSize(100, 100, format), mimeType); res.Should().HaveStatusCode(HttpStatusCode.OK); } @@ -222,22 +221,22 @@ namespace Timeline.Tests.IntegratedTests for (int i = 0; i < 2; i++) // double delete should work. { - var res = await client.DeleteAsync("users/user/avatar"); + var res = await client.DeleteAsync("users/user1/avatar"); res.Should().HaveStatusCode(200); await GetReturnDefault(); } } // Authorization check. - using (var client = await CreateClientAsAdmin()) + using (var client = await CreateClientAsAdministrator()) { { - var res = await client.PutByteArrayAsync("users/user/avatar", mockAvatar.Data, mockAvatar.Type); + var res = await client.PutByteArrayAsync("users/user1/avatar", mockAvatar.Data, mockAvatar.Type); res.Should().HaveStatusCode(HttpStatusCode.OK); } { - var res = await client.DeleteAsync("users/user/avatar"); + var res = await client.DeleteAsync("users/user1/avatar"); res.Should().HaveStatusCode(HttpStatusCode.OK); } @@ -256,7 +255,7 @@ namespace Timeline.Tests.IntegratedTests } // bad username check - using (var client = await CreateClientAsAdmin()) + using (var client = await CreateClientAsAdministrator()) { { var res = await client.GetAsync("users/u!ser/avatar"); diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs deleted file mode 100644 index 3781a816..00000000 --- a/Timeline.Tests/IntegratedTests/UserDetailTest.cs +++ /dev/null @@ -1,154 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Mvc.Testing; -using System.Net; -using System.Net.Http.Headers; -using System.Net.Mime; -using System.Threading.Tasks; -using Timeline.Models.Http; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.IntegratedTests -{ - public class UserDetailTest : IntegratedTestBase - { - public UserDetailTest(WebApplicationFactory factory) - : base(factory) - { - - } - - [Fact] - public async Task PermissionTest() - { - { // unauthorize - using var client = await CreateClientWithNoAuth(); - { // GET - var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname"); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - { // PUT - var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa"); - res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); - } - { // DELETE - var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname"); - res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); - } - } - { // user - using var client = await CreateClientAsUser(); - { // GET - var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname"); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - { // PUT self - var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa"); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - { // PUT other - var res = await client.PutStringAsync($"users/{MockUser.Admin.Username}/nickname", "aaa"); - res.Should().HaveStatusCode(HttpStatusCode.Forbidden); - } - { // DELETE self - var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname"); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - { // DELETE other - var res = await client.DeleteAsync($"users/{MockUser.Admin.Username}/nickname"); - res.Should().HaveStatusCode(HttpStatusCode.Forbidden); - } - } - { // user - using var client = await CreateClientAsAdmin(); - { // PUT other - var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa"); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - { // DELETE other - var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname"); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - } - } - - [Fact] - public async Task FunctionTest() - { - var url = $"users/{MockUser.User.Username}/nickname"; - var userNotExistUrl = "users/usernotexist/nickname"; - { - using var client = await CreateClientAsUser(); - { - var res = await client.GetAsync(userNotExistUrl); - res.Should().HaveStatusCode(HttpStatusCode.NotFound) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); - - } - { - var res = await client.GetAsync(url); - res.Should().HaveStatusCode(HttpStatusCode.OK); - res.Content.Headers.ContentType.Should().Be(new MediaTypeHeaderValue(MediaTypeNames.Text.Plain) { CharSet = "utf-8" }); - var body = await res.Content.ReadAsStringAsync(); - body.Should().Be(MockUser.User.Username); - } - { - var res = await client.PutStringAsync(url, ""); - res.Should().BeInvalidModel(); - } - { - var res = await client.PutStringAsync(url, new string('a', 11)); - res.Should().BeInvalidModel(); - } - var nickname1 = "nnn"; - var nickname2 = "nn2"; - { - var res = await client.PutStringAsync(url, nickname1); - res.Should().HaveStatusCode(HttpStatusCode.OK); - (await client.GetStringAsync(url)).Should().Be(nickname1); - } - { - var res = await client.PutStringAsync(url, nickname2); - res.Should().HaveStatusCode(HttpStatusCode.OK); - (await client.GetStringAsync(url)).Should().Be(nickname2); - } - { - var res = await client.DeleteAsync(url); - res.Should().HaveStatusCode(HttpStatusCode.OK); - (await client.GetStringAsync(url)).Should().Be(MockUser.User.Username); - } - { - var res = await client.DeleteAsync(url); - res.Should().HaveStatusCode(HttpStatusCode.OK); - } - } - { - using var client = await CreateClientAsAdmin(); - { - var res = await client.PutStringAsync(userNotExistUrl, "aaa"); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); - } - { - var res = await client.DeleteAsync(userNotExistUrl); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); - } - var nickname = "nnn"; - { - var res = await client.PutStringAsync(url, nickname); - res.Should().HaveStatusCode(HttpStatusCode.OK); - (await client.GetStringAsync(url)).Should().Be(nickname); - } - { - var res = await client.DeleteAsync(url); - res.Should().HaveStatusCode(HttpStatusCode.OK); - (await client.GetStringAsync(url)).Should().Be(MockUser.User.Username); - } - } - } - } -} diff --git a/Timeline.Tests/IntegratedTests/UserTest.cs b/Timeline.Tests/IntegratedTests/UserTest.cs index ea9f1177..4c2ccf7a 100644 --- a/Timeline.Tests/IntegratedTests/UserTest.cs +++ b/Timeline.Tests/IntegratedTests/UserTest.cs @@ -1,9 +1,9 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Threading.Tasks; -using Timeline.Models; using Timeline.Models.Http; using Timeline.Tests.Helpers; using Xunit; @@ -19,102 +19,144 @@ namespace Timeline.Tests.IntegratedTests } [Fact] - public async Task Get_List_Success() + public async Task GetList_NoAuth() { - using var client = await CreateClientAsAdmin(); - var res = await client.GetAsync("users"); + using var client = await CreateDefaultClient(); + var res = await client.GetAsync("/users"); res.Should().HaveStatusCode(200) - .And.HaveJsonBody() - .Which.Should().BeEquivalentTo(MockUser.UserInfoList); + .And.HaveJsonBody() + .Which.Should().BeEquivalentTo(UserInfoList); } [Fact] - public async Task Get_Single_Success() + public async Task GetList_User() { - using var client = await CreateClientAsAdmin(); - var res = await client.GetAsync("users/" + MockUser.User.Username); + using var client = await CreateClientAsUser(); + var res = await client.GetAsync("/users"); res.Should().HaveStatusCode(200) - .And.HaveJsonBody() - .Which.Should().BeEquivalentTo(MockUser.User.Info); + .And.HaveJsonBody() + .Which.Should().BeEquivalentTo(UserInfoList); } [Fact] - public async Task Get_InvalidModel() + public async Task GetList_Admin() { - using var client = await CreateClientAsAdmin(); - var res = await client.GetAsync("users/aaa!a"); - res.Should().BeInvalidModel(); + using var client = await CreateClientAsAdministrator(); + var res = await client.GetAsync("/users"); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Should().BeEquivalentTo(UserInfoForAdminList); } [Fact] - public async Task Get_Users_404() + public async Task Get_NoAuth() { - using var client = await CreateClientAsAdmin(); - var res = await client.GetAsync("users/usernotexist"); - res.Should().HaveStatusCode(404) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); + using var client = await CreateDefaultClient(); + var res = await client.GetAsync($"/users/admin"); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Should().BeEquivalentTo(UserInfoList[0]); } - public static IEnumerable Put_InvalidModel_Data() + [Fact] + public async Task Get_User() { - yield return new object[] { "aaa", null, false }; - yield return new object[] { "aaa", "p", null }; - yield return new object[] { "aa!a", "p", false }; + using var client = await CreateClientAsUser(); + var res = await client.GetAsync($"/users/admin"); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Should().BeEquivalentTo(UserInfoList[0]); } - [Theory] - [MemberData(nameof(Put_InvalidModel_Data))] - public async Task Put_InvalidModel(string username, string password, bool? administrator) + [Fact] + public async Task Get_Admin() { - using var client = await CreateClientAsAdmin(); - (await client.PutAsJsonAsync("users/" + username, - new UserPutRequest { Password = password, Administrator = administrator })) - .Should().BeInvalidModel(); + using var client = await CreateClientAsAdministrator(); + var res = await client.GetAsync($"/users/user1"); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Should().BeEquivalentTo(UserInfoForAdminList[1]); } - private async Task CheckAdministrator(HttpClient client, string username, bool administrator) + [Fact] + public async Task Get_InvalidModel() { - var res = await client.GetAsync("users/" + username); - res.Should().HaveStatusCode(200) - .And.HaveJsonBody() - .Which.Administrator.Should().Be(administrator); + using var client = await CreateClientAsUser(); + var res = await client.GetAsync("/users/aaa!a"); + res.Should().BeInvalidModel(); } [Fact] - public async Task Put_Modiefied() + public async Task Get_404() { - using var client = await CreateClientAsAdmin(); - var res = await client.PutAsJsonAsync("users/" + MockUser.User.Username, new UserPutRequest + using var client = await CreateClientAsUser(); + var res = await client.GetAsync("/users/usernotexist"); + res.Should().HaveStatusCode(404) + .And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + + [Fact] + public async Task Patch_User() + { + using var client = await CreateClientAsUser(); { - Password = "password", - Administrator = false - }); - res.Should().BePut(false); - await CheckAdministrator(client, MockUser.User.Username, false); + var res = await client.PatchAsJsonAsync("/users/user1", + new UserPatchRequest { Nickname = "aaa" }); + res.Should().HaveStatusCode(200); + } + + { + var res = await client.GetAsync("/users/user1"); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Nickname.Should().Be("aaa"); + } } [Fact] - public async Task Put_Created() + public async Task Patch_Admin() { - using var client = await CreateClientAsAdmin(); - const string username = "puttest"; - const string url = "users/" + username; + using var client = await CreateClientAsAdministrator(); + using var userClient = await CreateClientAsUser(); + + { + var res = await client.PatchAsJsonAsync("/users/user1", + new UserPatchRequest + { + Username = "newuser", + Password = "newpw", + Administrator = true, + Nickname = "aaa" + }); + res.Should().HaveStatusCode(200); + } - var res = await client.PutAsJsonAsync(url, new UserPutRequest { - Password = "password", - Administrator = false - }); - res.Should().BePut(true); - await CheckAdministrator(client, username, false); + var res = await client.GetAsync("/users/newuser"); + var body = res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which; + body.Administrator.Should().Be(true); + body.Nickname.Should().Be("aaa"); + } + + { + // Token should expire. + var res = await userClient.GetAsync("/users"); + res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } + + { + // Check password. + (await CreateClientWithCredential("newuser", "newpw")).Dispose(); + } } [Fact] public async Task Patch_NotExist() { - using var client = await CreateClientAsAdmin(); - var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { }); + using var client = await CreateClientAsAdministrator(); + var res = await client.PatchAsJsonAsync("/users/usernotexist", new UserPatchRequest { }); res.Should().HaveStatusCode(404) .And.HaveCommonBody() .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); @@ -123,114 +165,239 @@ namespace Timeline.Tests.IntegratedTests [Fact] public async Task Patch_InvalidModel() { - using var client = await CreateClientAsAdmin(); - var res = await client.PatchAsJsonAsync("users/aaa!a", new UserPatchRequest { }); + using var client = await CreateClientAsAdministrator(); + var res = await client.PatchAsJsonAsync("/users/aaa!a", new UserPatchRequest { }); + res.Should().BeInvalidModel(); + } + + public static IEnumerable Patch_InvalidModel_Body_Data() + { + yield return new[] { new UserPatchRequest { Username = "aaa!a" } }; + yield return new[] { new UserPatchRequest { Password = "" } }; + yield return new[] { new UserPatchRequest { Nickname = new string('a', 50) } }; + } + + [Theory] + [MemberData(nameof(Patch_InvalidModel_Body_Data))] + public async Task Patch_InvalidModel_Body(UserPatchRequest body) + { + using var client = await CreateClientAsAdministrator(); + var res = await client.PatchAsJsonAsync("/users/user1", body); res.Should().BeInvalidModel(); } [Fact] - public async Task Patch_Success() + public async Task Patch_UsernameConflict() { - using var client = await CreateClientAsAdmin(); - { - var res = await client.PatchAsJsonAsync("users/" + MockUser.User.Username, - new UserPatchRequest { Administrator = false }); - res.Should().HaveStatusCode(200); - await CheckAdministrator(client, MockUser.User.Username, false); - } + using var client = await CreateClientAsAdministrator(); + var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Username = "admin" }); + res.Should().HaveStatusCode(400) + .And.HaveCommonBody(ErrorCodes.UserController.UsernameConflict); } [Fact] - public async Task Delete_InvalidModel() + public async Task Patch_NoAuth_Unauthorized() { - using var client = await CreateClientAsAdmin(); - var url = "users/aaa!a"; - var res = await client.DeleteAsync(url); - res.Should().BeInvalidModel(); + using var client = await CreateClientAsUser(); + var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Nickname = "aaa" }); + res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task Patch_User_Forbid() + { + using var client = await CreateClientAsUser(); + var res = await client.PatchAsJsonAsync("/users/admin", new UserPatchRequest { Nickname = "aaa" }); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Fact] + public async Task Patch_Username_Forbid() + { + using var client = await CreateClientAsUser(); + var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Username = "aaa" }); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Fact] + public async Task Patch_Password_Forbid() + { + using var client = await CreateClientAsUser(); + var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Password = "aaa" }); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Fact] + public async Task Patch_Administrator_Forbid() + { + using var client = await CreateClientAsUser(); + var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Administrator = true }); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden); } [Fact] public async Task Delete_Deleted() { - using var client = await CreateClientAsAdmin(); - var url = "users/" + MockUser.User.Username; - var res = await client.DeleteAsync(url); - res.Should().BeDelete(true); + using var client = await CreateClientAsAdministrator(); + { + var res = await client.DeleteAsync("/users/user1"); + res.Should().BeDelete(true); + } - var res2 = await client.GetAsync(url); - res2.Should().HaveStatusCode(404); + { + var res = await client.GetAsync("/users/user1"); + res.Should().HaveStatusCode(404); + } } [Fact] public async Task Delete_NotExist() { - using var client = await CreateClientAsAdmin(); - var res = await client.DeleteAsync("users/usernotexist"); + using var client = await CreateClientAsAdministrator(); + var res = await client.DeleteAsync("/users/usernotexist"); res.Should().BeDelete(false); } - private const string changeUsernameUrl = "userop/changeusername"; + [Fact] + public async Task Delete_InvalidModel() + { + using var client = await CreateClientAsAdministrator(); + var res = await client.DeleteAsync("/users/aaa!a"); + res.Should().BeInvalidModel(); + } - public static IEnumerable Op_ChangeUsername_InvalidModel_Data() + [Fact] + public async Task Delete_NoAuth_Unauthorized() { - yield return new[] { null, "uuu" }; - yield return new[] { "uuu", null }; - yield return new[] { "a!a", "uuu" }; - yield return new[] { "uuu", "a!a" }; + using var client = await CreateDefaultClient(); + var res = await client.DeleteAsync("/users/aaa!a"); + res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } - [Theory] - [MemberData(nameof(Op_ChangeUsername_InvalidModel_Data))] - public async Task Op_ChangeUsername_InvalidModel(string oldUsername, string newUsername) + [Fact] + public async Task Delete_User_Forbid() { - using var client = await CreateClientAsAdmin(); - (await client.PostAsJsonAsync(changeUsernameUrl, - new ChangeUsernameRequest { OldUsername = oldUsername, NewUsername = newUsername })) - .Should().BeInvalidModel(); + using var client = await CreateClientAsUser(); + var res = await client.DeleteAsync("/users/aaa!a"); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden); } + private const string createUserUrl = "/userop/createuser"; + [Fact] - public async Task Op_ChangeUsername_UserNotExist() + public async Task Op_CreateUser() { - using var client = await CreateClientAsAdmin(); - var res = await client.PostAsJsonAsync(changeUsernameUrl, - new ChangeUsernameRequest { OldUsername = "usernotexist", NewUsername = "newUsername" }); - res.Should().HaveStatusCode(400) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserCommon.NotExist); + using var client = await CreateClientAsAdministrator(); + { + var res = await client.PostAsJsonAsync(createUserUrl, new CreateUserRequest + { + Username = "aaa", + Password = "bbb", + Administrator = true, + Nickname = "ccc" + }); + res.Should().HaveStatusCode(200); + } + { + var res = await client.GetAsync("users/aaa"); + var body = res.Should().HaveStatusCode(200) + .And.HaveJsonBody().Which; + body.Username.Should().Be("aaa"); + body.Nickname.Should().Be("ccc"); + body.Administrator.Should().BeTrue(); + } + { + // Test password. + (await CreateClientWithCredential("aaa", "bbb")).Dispose(); + } + } + + public static IEnumerable Op_CreateUser_InvalidModel_Data() + { + yield return new[] { new CreateUserRequest { Username = "aaa", Password = "bbb" } }; + yield return new[] { new CreateUserRequest { Username = "aaa", Administrator = true } }; + yield return new[] { new CreateUserRequest { Password = "bbb", Administrator = true } }; + yield return new[] { new CreateUserRequest { Username = "a!a", Password = "bbb", Administrator = true } }; + yield return new[] { new CreateUserRequest { Username = "aaa", Password = "", Administrator = true } }; + yield return new[] { new CreateUserRequest { Username = "aaa", Password = "bbb", Administrator = true, Nickname = new string('a', 40) } }; + } + + [Theory] + [MemberData(nameof(Op_CreateUser_InvalidModel_Data))] + public async Task Op_CreateUser_InvalidModel(CreateUserRequest body) + { + using var client = await CreateClientAsAdministrator(); + { + var res = await client.PostAsJsonAsync(createUserUrl, body); + res.Should().BeInvalidModel(); + } } [Fact] - public async Task Op_ChangeUsername_UserAlreadyExist() + public async Task Op_CreateUser_UsernameConflict() { - using var client = await CreateClientAsAdmin(); - var res = await client.PostAsJsonAsync(changeUsernameUrl, - new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = MockUser.Admin.Username }); - res.Should().HaveStatusCode(400) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserController.ChangeUsername_Conflict); + using var client = await CreateClientAsAdministrator(); + { + var res = await client.PostAsJsonAsync(createUserUrl, new CreateUserRequest + { + Username = "user1", + Password = "bbb", + Administrator = false + }); + res.Should().HaveStatusCode(400) + .And.HaveCommonBody(ErrorCodes.UserController.UsernameConflict); + } } - private async Task TestLogin(string username, string password) + [Fact] + public async Task Op_CreateUser_NoAuth_Unauthorized() { - using var client = await CreateClientWithNoAuth(); - var response = await client.PostAsJsonAsync("token/create", new CreateTokenRequest { Username = username, Password = password }); - response.Should().HaveStatusCode(200) - .And.HaveJsonBody(); + using var client = await CreateDefaultClient(); + { + var res = await client.PostAsJsonAsync(createUserUrl, new CreateUserRequest + { + Username = "aaa", + Password = "bbb", + Administrator = false + }); + res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } } [Fact] - public async Task Op_ChangeUsername_Success() + public async Task Op_CreateUser_User_Forbid() { - using var client = await CreateClientAsAdmin(); - const string newUsername = "hahaha"; - var res = await client.PostAsJsonAsync(changeUsernameUrl, - new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = newUsername }); - res.Should().HaveStatusCode(200); - await TestLogin(newUsername, MockUser.User.Password); + using var client = await CreateClientAsUser(); + { + var res = await client.PostAsJsonAsync(createUserUrl, new CreateUserRequest + { + Username = "aaa", + Password = "bbb", + Administrator = false + }); + res.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } } - private const string changePasswordUrl = "userop/changepassword"; + private const string changePasswordUrl = "/userop/changepassword"; + + [Fact] + public async Task Op_ChangePassword() + { + using var client = await CreateClientAsUser(); + { + var res = await client.PostAsJsonAsync(changePasswordUrl, + new ChangePasswordRequest { OldPassword = "user1pw", NewPassword = "newpw" }); + res.Should().HaveStatusCode(200); + } + { + var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { }); + res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } + { + (await CreateClientWithCredential("user1", "newpw")).Dispose(); + } + } public static IEnumerable Op_ChangePassword_InvalidModel_Data() { @@ -243,9 +410,9 @@ namespace Timeline.Tests.IntegratedTests public async Task Op_ChangePassword_InvalidModel(string oldPassword, string newPassword) { using var client = await CreateClientAsUser(); - (await client.PostAsJsonAsync(changePasswordUrl, - new ChangePasswordRequest { OldPassword = oldPassword, NewPassword = newPassword })) - .Should().BeInvalidModel(); + var res = await client.PostAsJsonAsync(changePasswordUrl, + new ChangePasswordRequest { OldPassword = oldPassword, NewPassword = newPassword }); + res.Should().BeInvalidModel(); } [Fact] @@ -254,19 +421,15 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsUser(); var res = await client.PostAsJsonAsync(changePasswordUrl, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" }); res.Should().HaveStatusCode(400) - .And.HaveCommonBody() - .Which.Code.Should().Be(ErrorCodes.UserController.ChangePassword_BadOldPassword); + .And.HaveCommonBody(ErrorCodes.UserController.ChangePassword_BadOldPassword); } [Fact] - public async Task Op_ChangePassword_Success() + public async Task Op_ChangePassword_NoAuth_Unauthorized() { - using var client = await CreateClientAsUser(); - const string newPassword = "new"; - var res = await client.PostAsJsonAsync(changePasswordUrl, - new ChangePasswordRequest { OldPassword = MockUser.User.Password, NewPassword = newPassword }); - res.Should().HaveStatusCode(200); - await TestLogin(MockUser.User.Username, newPassword); + using var client = await CreateDefaultClient(); + var res = await client.PostAsJsonAsync(changePasswordUrl, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" }); + res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } } } diff --git a/Timeline.Tests/Services/UserAvatarServiceTest.cs b/Timeline.Tests/Services/UserAvatarServiceTest.cs deleted file mode 100644 index 2dca7ccf..00000000 --- a/Timeline.Tests/Services/UserAvatarServiceTest.cs +++ /dev/null @@ -1,280 +0,0 @@ -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using SixLabors.ImageSharp.Formats.Png; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class UserAvatarValidatorTest : IClassFixture - { - private readonly UserAvatarValidator _validator; - - public UserAvatarValidatorTest(UserAvatarValidator validator) - { - _validator = validator; - } - - [Fact] - public void CantDecode() - { - Avatar avatar = new Avatar - { - Data = Encoding.ASCII.GetBytes("This is not a image."), - Type = "image/png" - }; - _validator.Awaiting(v => v.Validate(avatar)) - .Should().Throw() - .Where(e => e.Avatar == avatar && e.Error == AvatarFormatException.ErrorReason.CantDecode); - } - - [Fact] - public void UnmatchedFormat() - { - Avatar avatar = new Avatar - { - Data = ImageHelper.CreatePngWithSize(100, 100), - Type = "image/jpeg" - }; - _validator.Awaiting(v => v.Validate(avatar)) - .Should().Throw() - .Where(e => e.Avatar == avatar && e.Error == AvatarFormatException.ErrorReason.UnmatchedFormat); - } - - [Fact] - public void BadSize() - { - Avatar avatar = new Avatar - { - Data = ImageHelper.CreatePngWithSize(100, 200), - Type = PngFormat.Instance.DefaultMimeType - }; - _validator.Awaiting(v => v.Validate(avatar)) - .Should().Throw() - .Where(e => e.Avatar == avatar && e.Error == AvatarFormatException.ErrorReason.BadSize); - } - - [Fact] - public void Success() - { - Avatar avatar = new Avatar - { - Data = ImageHelper.CreatePngWithSize(100, 100), - Type = PngFormat.Instance.DefaultMimeType - }; - _validator.Awaiting(v => v.Validate(avatar)) - .Should().NotThrow(); - } - } - - public class UserAvatarServiceTest : IDisposable - { - private UserAvatarEntity CreateMockAvatarEntity(string key) => new UserAvatarEntity - { - Type = $"image/test{key}", - Data = Encoding.ASCII.GetBytes($"mock{key}"), - ETag = $"etag{key}", - LastModified = DateTime.Now - }; - - private AvatarInfo CreateMockAvatarInfo(string key) => new AvatarInfo - { - Avatar = new Avatar - { - Type = $"image/test{key}", - Data = Encoding.ASCII.GetBytes($"mock{key}") - }, - LastModified = DateTime.Now - }; - - private Avatar CreateMockAvatar(string key) => new Avatar - { - Type = $"image/test{key}", - Data = Encoding.ASCII.GetBytes($"mock{key}") - }; - - private static Avatar ToAvatar(UserAvatarEntity entity) - { - return new Avatar - { - Data = entity.Data, - Type = entity.Type - }; - } - - private static AvatarInfo ToAvatarInfo(UserAvatarEntity entity) - { - return new AvatarInfo - { - Avatar = ToAvatar(entity), - LastModified = entity.LastModified - }; - } - - private readonly Mock _mockDefaultAvatarProvider; - private readonly Mock _mockValidator; - private readonly Mock _mockETagGenerator; - private readonly Mock _mockClock; - - private readonly TestDatabase _database; - - private readonly UserAvatarService _service; - - public UserAvatarServiceTest() - { - _mockDefaultAvatarProvider = new Mock(); - _mockValidator = new Mock(); - _mockETagGenerator = new Mock(); - _mockClock = new Mock(); - - _database = new TestDatabase(); - - _service = new UserAvatarService(NullLogger.Instance, _database.Context, _mockDefaultAvatarProvider.Object, _mockValidator.Object, _mockETagGenerator.Object, _mockClock.Object); - } - - public void Dispose() - { - _database.Dispose(); - } - - [Theory] - [InlineData(null, typeof(ArgumentNullException))] - [InlineData("", typeof(UsernameBadFormatException))] - [InlineData("a!a", typeof(UsernameBadFormatException))] - [InlineData("usernotexist", typeof(UserNotExistException))] - public async Task GetAvatarETag_ShouldThrow(string username, Type exceptionType) - { - await _service.Awaiting(s => s.GetAvatarETag(username)).Should().ThrowAsync(exceptionType); - } - - [Fact] - public async Task GetAvatarETag_ShouldReturn_Default() - { - const string etag = "aaaaaa"; - _mockDefaultAvatarProvider.Setup(p => p.GetDefaultAvatarETag()).ReturnsAsync(etag); - (await _service.GetAvatarETag(MockUser.User.Username)).Should().Be(etag); - } - - [Fact] - public async Task GetAvatarETag_ShouldReturn_Data() - { - string username = MockUser.User.Username; - var mockAvatarEntity = CreateMockAvatarEntity("aaa"); - { - var context = _database.Context; - var user = await context.Users.Where(u => u.Username == username).Include(u => u.Avatar).SingleAsync(); - user.Avatar = mockAvatarEntity; - await context.SaveChangesAsync(); - } - (await _service.GetAvatarETag(username)).Should().BeEquivalentTo(mockAvatarEntity.ETag); - } - - [Theory] - [InlineData(null, typeof(ArgumentNullException))] - [InlineData("", typeof(UsernameBadFormatException))] - [InlineData("a!a", typeof(UsernameBadFormatException))] - [InlineData("usernotexist", typeof(UserNotExistException))] - public async Task GetAvatar_ShouldThrow(string username, Type exceptionType) - { - await _service.Awaiting(s => s.GetAvatar(username)).Should().ThrowAsync(exceptionType); - - } - - [Fact] - public async Task GetAvatar_ShouldReturn_Default() - { - var mockAvatar = CreateMockAvatarInfo("aaa"); - _mockDefaultAvatarProvider.Setup(p => p.GetDefaultAvatar()).ReturnsAsync(mockAvatar); - string username = MockUser.User.Username; - (await _service.GetAvatar(username)).Should().BeEquivalentTo(mockAvatar); - } - - [Fact] - public async Task GetAvatar_ShouldReturn_Data() - { - string username = MockUser.User.Username; - var mockAvatarEntity = CreateMockAvatarEntity("aaa"); - { - var context = _database.Context; - var user = await context.Users.Where(u => u.Username == username).Include(u => u.Avatar).SingleAsync(); - user.Avatar = mockAvatarEntity; - await context.SaveChangesAsync(); - } - - (await _service.GetAvatar(username)).Should().BeEquivalentTo(ToAvatarInfo(mockAvatarEntity)); - } - - public static IEnumerable SetAvatar_ShouldThrow_Data() - { - yield return new object[] { null, null, typeof(ArgumentNullException) }; - yield return new object[] { "", null, typeof(UsernameBadFormatException) }; - yield return new object[] { "u!u", null, typeof(UsernameBadFormatException) }; - yield return new object[] { null, new Avatar { Type = null, Data = new[] { (byte)0x00 } }, typeof(ArgumentException) }; - yield return new object[] { null, new Avatar { Type = "", Data = new[] { (byte)0x00 } }, typeof(ArgumentException) }; - yield return new object[] { null, new Avatar { Type = "aaa", Data = null }, typeof(ArgumentException) }; - yield return new object[] { "usernotexist", null, typeof(UserNotExistException) }; - } - - [Theory] - [MemberData(nameof(SetAvatar_ShouldThrow_Data))] - public async Task SetAvatar_ShouldThrow(string username, Avatar avatar, Type exceptionType) - { - await _service.Awaiting(s => s.SetAvatar(username, avatar)).Should().ThrowAsync(exceptionType); - } - - [Fact] - public async Task SetAvatar_Should_Work() - { - string username = MockUser.User.Username; - - var user = await _database.Context.Users.Where(u => u.Username == username).Include(u => u.Avatar).SingleAsync(); - - var avatar1 = CreateMockAvatar("aaa"); - var avatar2 = CreateMockAvatar("bbb"); - - string etag1 = "etagaaa"; - string etag2 = "etagbbb"; - - DateTime dateTime1 = DateTime.Now.AddSeconds(2); - DateTime dateTime2 = DateTime.Now.AddSeconds(10); - DateTime dateTime3 = DateTime.Now.AddSeconds(20); - - // create - _mockETagGenerator.Setup(g => g.Generate(avatar1.Data)).ReturnsAsync(etag1); - _mockClock.Setup(c => c.GetCurrentTime()).Returns(dateTime1); - await _service.SetAvatar(username, avatar1); - user.Avatar.Should().NotBeNull(); - user.Avatar.Type.Should().Be(avatar1.Type); - user.Avatar.Data.Should().Equal(avatar1.Data); - user.Avatar.ETag.Should().Be(etag1); - user.Avatar.LastModified.Should().Be(dateTime1); - - // modify - _mockETagGenerator.Setup(g => g.Generate(avatar2.Data)).ReturnsAsync(etag2); - _mockClock.Setup(c => c.GetCurrentTime()).Returns(dateTime2); - await _service.SetAvatar(username, avatar2); - user.Avatar.Should().NotBeNull(); - user.Avatar.Type.Should().Be(avatar2.Type); - user.Avatar.Data.Should().Equal(avatar2.Data); - user.Avatar.ETag.Should().Be(etag2); - user.Avatar.LastModified.Should().Be(dateTime2); - - // delete - _mockClock.Setup(c => c.GetCurrentTime()).Returns(dateTime3); - await _service.SetAvatar(username, null); - user.Avatar.Type.Should().BeNull(); - user.Avatar.Data.Should().BeNull(); - user.Avatar.ETag.Should().BeNull(); - user.Avatar.LastModified.Should().Be(dateTime3); - } - } -} diff --git a/Timeline.Tests/Services/UserDetailServiceTest.cs b/Timeline.Tests/Services/UserDetailServiceTest.cs deleted file mode 100644 index dbff2705..00000000 --- a/Timeline.Tests/Services/UserDetailServiceTest.cs +++ /dev/null @@ -1,107 +0,0 @@ -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging.Abstractions; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class UserDetailServiceTest : IDisposable - { - private readonly TestDatabase _testDatabase; - - private readonly UserDetailService _service; - - public UserDetailServiceTest() - { - _testDatabase = new TestDatabase(); - _service = new UserDetailService(_testDatabase.Context, NullLogger.Instance); - } - - public void Dispose() - { - _testDatabase.Dispose(); - } - - [Theory] - [InlineData(null, typeof(ArgumentNullException))] - [InlineData("", typeof(UsernameBadFormatException))] - [InlineData("a!a", typeof(UsernameBadFormatException))] - [InlineData("usernotexist", typeof(UserNotExistException))] - public async Task GetNickname_ShouldThrow(string username, Type exceptionType) - { - await _service.Awaiting(s => s.GetNickname(username)).Should().ThrowAsync(exceptionType); - } - - [Fact] - public async Task GetNickname_ShouldReturnUsername() - { - var result = await _service.GetNickname(MockUser.User.Username); - result.Should().Be(MockUser.User.Username); - } - - [Fact] - public async Task GetNickname_ShouldReturnData() - { - const string nickname = "aaaaaa"; - { - var context = _testDatabase.Context; - var userId = (await context.Users.Where(u => u.Username == MockUser.User.Username).Select(u => new { u.Id }).SingleAsync()).Id; - context.UserDetails.Add(new UserDetailEntity - { - Nickname = nickname, - UserId = userId - }); - await context.SaveChangesAsync(); - } - var result = await _service.GetNickname(MockUser.User.Username); - result.Should().Be(nickname); - } - - [Theory] - [InlineData(null, typeof(ArgumentNullException))] - [InlineData("", typeof(UsernameBadFormatException))] - [InlineData("a!a", typeof(UsernameBadFormatException))] - [InlineData("usernotexist", typeof(UserNotExistException))] - public async Task SetNickname_ShouldThrow(string username, Type exceptionType) - { - await _service.Awaiting(s => s.SetNickname(username, null)).Should().ThrowAsync(exceptionType); - } - - [Fact] - public async Task SetNickname_ShouldThrow_ArgumentException() - { - await _service.Awaiting(s => s.SetNickname("uuu", new string('a', 50))).Should().ThrowAsync(); - } - - [Fact] - public async Task SetNickname_ShouldWork() - { - var username = MockUser.User.Username; - var user = await _testDatabase.Context.Users.Where(u => u.Username == username).Include(u => u.Detail).SingleAsync(); - - var nickname1 = "nickname1"; - var nickname2 = "nickname2"; - - await _service.SetNickname(username, null); - user.Detail.Should().BeNull(); - - await _service.SetNickname(username, nickname1); - user.Detail.Should().NotBeNull(); - user.Detail.Nickname.Should().Be(nickname1); - - await _service.SetNickname(username, nickname2); - user.Detail.Should().NotBeNull(); - user.Detail.Nickname.Should().Be(nickname2); - - await _service.SetNickname(username, null); - user.Detail.Should().NotBeNull(); - user.Detail.Nickname.Should().BeNull(); - } - } -} diff --git a/Timeline.Tests/Services/UserTokenManagerTest.cs b/Timeline.Tests/Services/UserTokenManagerTest.cs deleted file mode 100644 index e649fbab..00000000 --- a/Timeline.Tests/Services/UserTokenManagerTest.cs +++ /dev/null @@ -1,163 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using System; -using System.Threading.Tasks; -using Timeline.Models; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class UserTokenManagerTest - { - private readonly UserTokenManager _service; - - private readonly Mock _mockUserService; - private readonly Mock _mockUserTokenService; - private readonly TestClock _mockClock; - - public UserTokenManagerTest() - { - _mockUserService = new Mock(); - _mockUserTokenService = new Mock(); - _mockClock = new TestClock(); - - _service = new UserTokenManager(NullLogger.Instance, _mockUserService.Object, _mockUserTokenService.Object, _mockClock); - } - - [Theory] - [InlineData(null, "aaa", "username")] - [InlineData("aaa", null, "password")] - public void CreateToken_NullArgument(string username, string password, string paramName) - { - _service.Invoking(s => s.CreateToken(username, password)).Should().Throw() - .Which.ParamName.Should().Be(paramName); - } - - [Theory] - [InlineData(typeof(UsernameBadFormatException))] - [InlineData(typeof(UserNotExistException))] - [InlineData(typeof(BadPasswordException))] - public async Task CreateToken_VerifyCredential_Throw(Type exceptionType) - { - const string username = "uuu"; - const string password = "ppp"; - _mockUserService.Setup(s => s.VerifyCredential(username, password)).ThrowsAsync((Exception)Activator.CreateInstance(exceptionType)); - await _service.Awaiting(s => s.CreateToken(username, password)).Should().ThrowAsync(exceptionType); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task CreateToken_Success(bool setExpireTime) - { - const string username = "uuu"; - const string password = "ppp"; - var mockExpireTime = setExpireTime ? (DateTime?)DateTime.Now : null; - var mockUserInfo = new User - { - Id = 1, - Username = username, - Administrator = false, - Version = 1 - }; - const string mockToken = "mocktokenaaaaaaa"; - - _mockUserService.Setup(s => s.VerifyCredential(username, password)).ReturnsAsync(mockUserInfo); - _mockUserTokenService.Setup(s => s.GenerateToken( - It.Is(userTokenInfo => - userTokenInfo.Id == mockUserInfo.Id && - userTokenInfo.Version == mockUserInfo.Version && - userTokenInfo.ExpireAt == mockExpireTime))).Returns(mockToken); - (await _service.CreateToken(username, password, mockExpireTime)) - .Should().BeEquivalentTo(new UserTokenCreateResult - { - Token = mockToken, - User = mockUserInfo - }); - } - - [Fact] - public void VerifyToken_NullArgument() - { - _service.Invoking(s => s.VerifyToken(null)).Should().ThrowAsync(); - } - - [Fact] - public async Task VerifyToken_BadFormat() - { - const string mockToken = "mocktokenaaaaaa"; - _mockUserTokenService.Setup(s => s.VerifyToken(mockToken)).Throws(new UserTokenBadFormatException()); - - await _service.Awaiting(s => s.VerifyToken(mockToken)).Should().ThrowAsync(); - } - - [Fact] - public async Task VerifyToken_TimeExpire() - { - const string mockToken = "mocktokenaaaaaa"; - var mockTime = DateTime.Now; - _mockClock.MockCurrentTime = mockTime; - var mockTokenInfo = new UserTokenInfo - { - Id = 1, - Version = 1, - ExpireAt = mockTime.AddDays(-1) - }; - _mockUserTokenService.Setup(s => s.VerifyToken(mockToken)).Returns(mockTokenInfo); - - await _service.Awaiting(s => s.VerifyToken(mockToken)).Should().ThrowAsync(); - } - - [Fact] - public async Task VerifyToken_BadVersion() - { - const string mockToken = "mocktokenaaaaaa"; - var mockTime = DateTime.Now; - _mockClock.MockCurrentTime = mockTime; - var mockTokenInfo = new UserTokenInfo - { - Id = 1, - Version = 1, - ExpireAt = mockTime.AddDays(1) - }; - _mockUserTokenService.Setup(s => s.VerifyToken(mockToken)).Returns(mockTokenInfo); - _mockUserService.Setup(s => s.GetUserById(1)).ReturnsAsync(new User - { - Id = 1, - Username = "aaa", - Administrator = false, - Version = 2 - }); - - await _service.Awaiting(s => s.VerifyToken(mockToken)).Should().ThrowAsync(); - } - - [Fact] - public async Task VerifyToken_Success() - { - const string mockToken = "mocktokenaaaaaa"; - var mockTime = DateTime.Now; - _mockClock.MockCurrentTime = mockTime; - var mockTokenInfo = new UserTokenInfo - { - Id = 1, - Version = 1, - ExpireAt = mockTime.AddDays(1) - }; - var mockUserInfo = new User - { - Id = 1, - Username = "aaa", - Administrator = false, - Version = 1 - }; - _mockUserTokenService.Setup(s => s.VerifyToken(mockToken)).Returns(mockTokenInfo); - _mockUserService.Setup(s => s.GetUserById(1)).ReturnsAsync(mockUserInfo); - - (await _service.VerifyToken(mockToken)).Should().BeEquivalentTo(mockUserInfo); - } - } -} diff --git a/Timeline.Tests/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj index bde4f430..40e8cda2 100644 --- a/Timeline.Tests/Timeline.Tests.csproj +++ b/Timeline.Tests/Timeline.Tests.csproj @@ -18,7 +18,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - -- cgit v1.2.3