diff options
Diffstat (limited to 'Timeline.Tests')
30 files changed, 1314 insertions, 1594 deletions
diff --git a/Timeline.Tests/AuthorizationUnitTest.cs b/Timeline.Tests/AuthorizationUnitTest.cs deleted file mode 100644 index 4751e95f..00000000 --- a/Timeline.Tests/AuthorizationUnitTest.cs +++ /dev/null @@ -1,76 +0,0 @@ -using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using System;
-using System.Net;
-using System.Threading.Tasks;
-using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests
-{
- public class AuthorizationUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
- {
- private const string AuthorizeUrl = "Test/User/Authorize";
- private const string UserUrl = "Test/User/User";
- private const string AdminUrl = "Test/User/Admin";
-
- private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
-
- public AuthorizationUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
- {
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
- }
-
- public void Dispose()
- {
- _disposeAction();
- }
-
- [Fact]
- public async Task UnauthenticationTest()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var response = await client.GetAsync(AuthorizeUrl);
- response.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
- }
- }
-
- [Fact]
- public async Task AuthenticationTest()
- {
- using (var client = await _factory.CreateClientAsUser())
- {
- var response = await client.GetAsync(AuthorizeUrl);
- response.Should().HaveStatusCode(HttpStatusCode.OK);
- }
- }
-
- [Fact]
- public async Task UserAuthorizationTest()
- {
- using (var client = await _factory.CreateClientAsUser())
- {
- var response1 = await client.GetAsync(UserUrl);
- response1.Should().HaveStatusCode(HttpStatusCode.OK);
- var response2 = await client.GetAsync(AdminUrl);
- response2.Should().HaveStatusCode(HttpStatusCode.Forbidden);
- }
- }
-
- [Fact]
- public async Task AdminAuthorizationTest()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var response1 = await client.GetAsync(UserUrl);
- response1.Should().HaveStatusCode(HttpStatusCode.OK);
- var response2 = await client.GetAsync(AdminUrl);
- response2.Should().HaveStatusCode(HttpStatusCode.OK);
- }
- }
- }
-}
diff --git a/Timeline.Tests/Controllers/TokenControllerTest.cs b/Timeline.Tests/Controllers/TokenControllerTest.cs new file mode 100644 index 00000000..53b6c606 --- /dev/null +++ b/Timeline.Tests/Controllers/TokenControllerTest.cs @@ -0,0 +1,122 @@ +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.Http;
+using Timeline.Services;
+using Timeline.Tests.Mock.Data;
+using Timeline.Tests.Mock.Services;
+using Xunit;
+using static Timeline.ErrorCodes.Http.Token;
+
+namespace Timeline.Tests.Controllers
+{
+ public class TokenControllerTest : IDisposable
+ {
+ private readonly Mock<IUserService> _mockUserService = new Mock<IUserService>();
+ private readonly TestClock _mockClock = new TestClock();
+
+
+ private readonly TokenController _controller;
+
+ public TokenControllerTest()
+ {
+ _controller = new TokenController(_mockUserService.Object,
+ NullLogger<TokenController>.Instance, _mockClock,
+ TestStringLocalizerFactory.Create().Create<TokenController>());
+ }
+
+ 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 createResult = new CreateTokenResult
+ {
+ Token = "mocktokenaaaaa",
+ User = MockUser.User.Info
+ };
+ _mockUserService.Setup(s => s.CreateToken("u", "p", expire == null ? null : (DateTime?)mockCurrentTime.AddDays(expire.Value))).ReturnsAsync(createResult);
+ var action = await _controller.Create(new CreateTokenRequest
+ {
+ Username = "u",
+ Password = "p",
+ Expire = expire
+ });
+ action.Result.Should().BeAssignableTo<OkObjectResult>()
+ .Which.Value.Should().BeEquivalentTo(createResult);
+ }
+
+ [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<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(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<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(Create.BadCredential);
+ }
+
+ [Fact]
+ public async Task Verify_Ok()
+ {
+ const string token = "aaaaaaaaaaaaaa";
+ _mockUserService.Setup(s => s.VerifyToken(token)).ReturnsAsync(MockUser.User.Info);
+ var action = await _controller.Verify(new VerifyTokenRequest { Token = token });
+ action.Result.Should().BeAssignableTo<OkObjectResult>()
+ .Which.Value.Should().BeAssignableTo<VerifyTokenResponse>()
+ .Which.User.Should().BeEquivalentTo(MockUser.User.Info);
+ }
+
+ public static IEnumerable<object[]> Verify_BadRequest_Data()
+ {
+ yield return new object[] { new JwtVerifyException(JwtVerifyException.ErrorCodes.Expired), Verify.Expired };
+ yield return new object[] { new JwtVerifyException(JwtVerifyException.ErrorCodes.IdClaimBadFormat), Verify.BadFormat };
+ yield return new object[] { new JwtVerifyException(JwtVerifyException.ErrorCodes.OldVersion), Verify.OldVersion };
+ yield return new object[] { new UserNotExistException(), 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<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(code);
+ }
+ }
+}
diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs new file mode 100644 index 00000000..781ec111 --- /dev/null +++ b/Timeline.Tests/Controllers/UserControllerTest.cs @@ -0,0 +1,222 @@ +using FluentAssertions;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using System;
+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 Timeline.Tests.Mock.Data;
+using Timeline.Tests.Mock.Services;
+using Xunit;
+using static Timeline.ErrorCodes.Http.User;
+
+namespace Timeline.Tests.Controllers
+{
+ public class UserControllerTest : IDisposable
+ {
+ private readonly Mock<IUserService> _mockUserService = new Mock<IUserService>();
+
+ private readonly UserController _controller;
+
+ public UserControllerTest()
+ {
+ _controller = new UserController(NullLogger<UserController>.Instance,
+ _mockUserService.Object,
+ TestStringLocalizerFactory.Create());
+ }
+
+ public void Dispose()
+ {
+ _controller.Dispose();
+ }
+
+ [Fact]
+ public async Task GetList_Success()
+ {
+ var array = MockUser.UserInfoList.ToArray();
+ _mockUserService.Setup(s => s.ListUsers()).ReturnsAsync(array);
+ var action = await _controller.List();
+ action.Result.Should().BeAssignableTo<OkObjectResult>()
+ .Which.Value.Should().BeEquivalentTo(array);
+ }
+
+ [Fact]
+ public async Task Get_Success()
+ {
+ const string username = "aaa";
+ _mockUserService.Setup(s => s.GetUser(username)).ReturnsAsync(MockUser.User.Info);
+ var action = await _controller.Get(username);
+ action.Result.Should().BeAssignableTo<OkObjectResult>()
+ .Which.Value.Should().BeEquivalentTo(MockUser.User.Info);
+ }
+
+ [Fact]
+ public async Task Get_NotFound()
+ {
+ const string username = "aaa";
+ _mockUserService.Setup(s => s.GetUser(username)).Returns(Task.FromResult<UserInfo>(null));
+ var action = await _controller.Get(username);
+ action.Result.Should().BeAssignableTo<NotFoundObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(Get.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<ObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonPutResponse>()
+ .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<OkResult>();
+ }
+
+ [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<NotFoundObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(Patch.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<OkObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonDeleteResponse>()
+ .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<OkObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonDeleteResponse>()
+ .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<OkResult>();
+ }
+
+ [Theory]
+ [InlineData(typeof(UserNotExistException), Op.ChangeUsername.NotExist)]
+ [InlineData(typeof(UsernameConfictException), Op.ChangeUsername.AlreadyExist)]
+ 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<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .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<OkResult>();
+ }
+
+ [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<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(Op.ChangePassword.BadOldPassword);
+ }
+ }
+}
diff --git a/Timeline.Tests/DatabaseTest.cs b/Timeline.Tests/DatabaseTest.cs index f75ab71b..b5681491 100644 --- a/Timeline.Tests/DatabaseTest.cs +++ b/Timeline.Tests/DatabaseTest.cs @@ -26,27 +26,21 @@ namespace Timeline.Tests [Fact]
public void DeleteUserShouldAlsoDeleteAvatar()
{
- _context.UserAvatars.Count().Should().Be(2);
var user = _context.Users.First();
- _context.Users.Remove(user);
- _context.SaveChanges();
- _context.UserAvatars.Count().Should().Be(1);
- }
-
- [Fact]
- public void DeleteUserShouldAlsoDeleteDetail()
- {
- var user = _context.Users.First();
- _context.UserDetails.Add(new UserDetailEntity
+ _context.UserAvatars.Count().Should().Be(0);
+ _context.UserAvatars.Add(new UserAvatar
{
+ Data = null,
+ Type = null,
+ ETag = null,
+ LastModified = DateTime.Now,
UserId = user.Id
});
_context.SaveChanges();
- _context.UserDetails.Count().Should().Be(1);
-
+ _context.UserAvatars.Count().Should().Be(1);
_context.Users.Remove(user);
_context.SaveChanges();
- _context.UserDetails.Count().Should().Be(0);
+ _context.UserAvatars.Count().Should().Be(0);
}
}
}
diff --git a/Timeline.Tests/GlobalSuppressions.cs b/Timeline.Tests/GlobalSuppressions.cs new file mode 100644 index 00000000..1d1d294b --- /dev/null +++ b/Timeline.Tests/GlobalSuppressions.cs @@ -0,0 +1,14 @@ +// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "This is not a UI application.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Tests name have underscores.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Test may catch all exceptions.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Test classes can be nested.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "This is redundant.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1063:Implement IDisposable Correctly", Justification = "Test classes do not need to implement it that way.")]
+[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.")]
diff --git a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs b/Timeline.Tests/Helpers/AssertionResponseExtensions.cs index e67a172a..08f10b2b 100644 --- a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs +++ b/Timeline.Tests/Helpers/AssertionResponseExtensions.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using System;
using System.Net;
using System.Net.Http;
+using System.Text;
using Timeline.Models.Http;
namespace Timeline.Tests.Helpers
@@ -23,8 +24,25 @@ namespace Timeline.Tests.Helpers string padding = new string('\t', context.Depth);
var res = (HttpResponseMessage)value;
- var body = res.Content.ReadAsStringAsync().Result;
- return $"{newline}{padding} Status Code: {res.StatusCode} ; Body: {body.Substring(0, Math.Min(body.Length, 20))} ;";
+
+ var builder = new StringBuilder();
+ builder.Append($"{newline}{padding} Status Code: {res.StatusCode} ; Body: ");
+
+ try
+ {
+ var body = res.Content.ReadAsStringAsync().Result;
+ if (body.Length > 40)
+ {
+ body = body[0..40] + " ...";
+ }
+ builder.Append(body);
+ }
+ catch (AggregateException)
+ {
+ builder.Append("NOT A STRING.");
+ }
+
+ return builder.ToString();
}
}
@@ -43,15 +61,20 @@ namespace Timeline.Tests.Helpers protected override string Identifier => "HttpResponseMessage";
+ public AndConstraint<HttpResponseMessage> HaveStatusCode(int expected, string because = "", params object[] becauseArgs)
+ {
+ return HaveStatusCode((HttpStatusCode)expected, because, becauseArgs);
+ }
+
public AndConstraint<HttpResponseMessage> HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs)
{
Execute.Assertion.BecauseOf(because, becauseArgs)
.ForCondition(Subject.StatusCode == expected)
- .FailWith("Expected status code of {context:HttpResponseMessage} to be {0}{reason}, but found {1}.\nResponse is {2}.", expected, Subject.StatusCode, Subject);
+ .FailWith("Expected status code of {context:HttpResponseMessage} to be {0}{reason}, but found {1}.", expected, Subject.StatusCode);
return new AndConstraint<HttpResponseMessage>(Subject);
}
- public AndWhichConstraint<HttpResponseMessage, T> HaveBodyAsJson<T>(string because = "", params object[] becauseArgs)
+ public AndWhichConstraint<HttpResponseMessage, T> HaveJsonBody<T>(string because = "", params object[] becauseArgs)
{
var a = Execute.Assertion.BecauseOf(because, becauseArgs);
string body;
@@ -59,22 +82,14 @@ namespace Timeline.Tests.Helpers {
body = Subject.Content.ReadAsStringAsync().Result;
}
- catch (Exception e)
+ catch (AggregateException e)
{
- a.FailWith("Failed to read response body of {context:HttpResponseMessage}{reason}.\nException is {0}.", e);
+ a.FailWith("Expected response body of {context:HttpResponseMessage} to be json string{reason}, but failed to read it or it was not a string. Exception is {0}.", e.InnerExceptions);
return new AndWhichConstraint<HttpResponseMessage, T>(Subject, null);
}
- try
- {
- var result = JsonConvert.DeserializeObject<T>(body);
- return new AndWhichConstraint<HttpResponseMessage, T>(Subject, result);
- }
- catch (Exception e)
- {
- a.FailWith("Failed to convert response body of {context:HttpResponseMessage} to {0}{reason}.\nResponse is {1}.\nException is {2}.", typeof(T).FullName, Subject, e);
- return new AndWhichConstraint<HttpResponseMessage, T>(Subject, null);
- }
+ var result = JsonConvert.DeserializeObject<T>(body);
+ return new AndWhichConstraint<HttpResponseMessage, T>(Subject, result);
}
}
@@ -85,54 +100,42 @@ namespace Timeline.Tests.Helpers return new HttpResponseMessageAssertions(instance);
}
- public static AndConstraint<HttpResponseMessage> HaveStatusCodeOk(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- return assertions.HaveStatusCode(HttpStatusCode.OK, because, becauseArgs);
- }
-
- public static AndConstraint<HttpResponseMessage> HaveStatusCodeCreated(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- return assertions.HaveStatusCode(HttpStatusCode.Created, because, becauseArgs);
- }
-
- public static AndConstraint<HttpResponseMessage> HaveStatusCodeBadRequest(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- return assertions.HaveStatusCode(HttpStatusCode.BadRequest, because, becauseArgs);
- }
-
- public static AndConstraint<HttpResponseMessage> HaveStatusCodeNotFound(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- return assertions.HaveStatusCode(HttpStatusCode.NotFound, because, becauseArgs);
- }
-
- public static AndWhichConstraint<HttpResponseMessage, CommonResponse> HaveBodyAsCommonResponse(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- return assertions.HaveBodyAsJson<CommonResponse>(because, becauseArgs);
- }
-
- public static void HaveBodyAsCommonResponseWithCode(this HttpResponseMessageAssertions assertions, int expected, string because = "", params object[] becauseArgs)
+ public static AndWhichConstraint<HttpResponseMessage, CommonResponse> HaveCommonBody(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
- assertions.HaveBodyAsCommonResponse(because, becauseArgs).Which.Code.Should().Be(expected, because, becauseArgs);
+ return assertions.HaveJsonBody<CommonResponse>(because, becauseArgs);
}
- public static void BePutCreated(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static AndWhichConstraint<HttpResponseMessage, CommonDataResponse<TData>> HaveCommonDataBody<TData>(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeCreated(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Created, because, becauseArgs);
+ return assertions.HaveJsonBody<CommonDataResponse<TData>>(because, becauseArgs);
}
- public static void BePutModified(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BePut(this HttpResponseMessageAssertions assertions, bool create, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Modified, because, becauseArgs);
+ var body = assertions.HaveStatusCode(create ? 201 : 200, because, becauseArgs)
+ .And.Should().HaveJsonBody<CommonPutResponse>(because, becauseArgs)
+ .Which;
+ body.Code.Should().Be(0);
+ body.Data.Create.Should().Be(create);
}
- public static void BeDeleteDeleted(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BeDelete(this HttpResponseMessageAssertions assertions, bool delete, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.Deleted, because, becauseArgs);
+ var body = assertions.HaveStatusCode(200, because, becauseArgs)
+ .And.Should().HaveJsonBody<CommonDeleteResponse>(because, becauseArgs)
+ .Which;
+ body.Code.Should().Be(0);
+ body.Data.Delete.Should().Be(delete);
}
- public static void BeDeleteNotExist(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BeInvalidModel(this HttpResponseMessageAssertions assertions, string message = null)
{
- assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.NotExists, because, becauseArgs);
+ message = string.IsNullOrEmpty(message) ? "" : ", " + message;
+ assertions.HaveStatusCode(400, "Invalid Model Error must have 400 status code{0}", message)
+ .And.Should().HaveCommonBody("Invalid Model Error must have CommonResponse body{0}", message)
+ .Which.Code.Should().Be(ErrorCodes.Http.Common.InvalidModel,
+ "Invalid Model Error must have code {0} in body{1}",
+ ErrorCodes.Http.Common.InvalidModel, message);
}
}
}
diff --git a/Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs b/Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs new file mode 100644 index 00000000..b78309c0 --- /dev/null +++ b/Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs @@ -0,0 +1,16 @@ +using FluentAssertions;
+using FluentAssertions.Primitives;
+using FluentAssertions.Specialized;
+using System;
+using System.Threading.Tasks;
+
+namespace Timeline.Tests.Helpers
+{
+ public static class AsyncFunctionAssertionsExtensions
+ {
+ public static async Task<AndConstraint<ObjectAssertions>> ThrowAsync(this AsyncFunctionAssertions assertions, Type exceptionType, string because = "", params object[] becauseArgs)
+ {
+ return (await assertions.ThrowAsync<Exception>(because, becauseArgs)).Which.Should().BeAssignableTo(exceptionType);
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs index c8a79e58..34d7e460 100644 --- a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs +++ b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs @@ -13,8 +13,8 @@ namespace Timeline.Tests.Helpers.Authentication public static async Task<CreateTokenResponse> CreateUserTokenAsync(this HttpClient client, string username, string password, int? expireOffset = null)
{
- var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, ExpireOffset = expireOffset });
- response.Should().HaveStatusCodeOk();
+ var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, Expire = expireOffset });
+ response.Should().HaveStatusCode(200);
var result = JsonConvert.DeserializeObject<CreateTokenResponse>(await response.Content.ReadAsStringAsync());
return result;
}
@@ -29,12 +29,12 @@ namespace Timeline.Tests.Helpers.Authentication public static Task<HttpClient> CreateClientAsUser<T>(this WebApplicationFactory<T> factory) where T : class
{
- return factory.CreateClientWithCredential(MockUsers.UserUsername, MockUsers.UserPassword);
+ return factory.CreateClientWithCredential(MockUser.User.Username, MockUser.User.Password);
}
public static Task<HttpClient> CreateClientAsAdmin<T>(this WebApplicationFactory<T> factory) where T : class
{
- return factory.CreateClientWithCredential(MockUsers.AdminUsername, MockUsers.AdminPassword);
+ return factory.CreateClientWithCredential(MockUser.Admin.Username, MockUser.Admin.Password);
}
}
}
diff --git a/Timeline.Tests/Helpers/HttpClientExtensions.cs b/Timeline.Tests/Helpers/HttpClientExtensions.cs index b9204fcc..38641f90 100644 --- a/Timeline.Tests/Helpers/HttpClientExtensions.cs +++ b/Timeline.Tests/Helpers/HttpClientExtensions.cs @@ -1,6 +1,8 @@ using Newtonsoft.Json;
+using System;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
@@ -10,11 +12,24 @@ namespace Timeline.Tests.Helpers {
public static Task<HttpResponseMessage> PatchAsJsonAsync<T>(this HttpClient client, string url, T body)
{
- return client.PatchAsync(url, new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"));
+ return client.PatchAsJsonAsync(new Uri(url, UriKind.RelativeOrAbsolute), body);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
+ public static Task<HttpResponseMessage> PatchAsJsonAsync<T>(this HttpClient client, Uri url, T body)
+ {
+ return client.PatchAsync(url, new StringContent(
+ JsonConvert.SerializeObject(body), Encoding.UTF8, MediaTypeNames.Application.Json));
}
public static Task<HttpResponseMessage> PutByteArrayAsync(this HttpClient client, string url, byte[] body, string mimeType)
{
+ return client.PutByteArrayAsync(new Uri(url, UriKind.RelativeOrAbsolute), body, mimeType);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
+ public static Task<HttpResponseMessage> PutByteArrayAsync(this HttpClient client, Uri url, byte[] body, string mimeType)
+ {
var content = new ByteArrayContent(body);
content.Headers.ContentLength = body.Length;
content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
diff --git a/Timeline.Tests/Helpers/ImageHelper.cs b/Timeline.Tests/Helpers/ImageHelper.cs index 2a2f3870..9bed0917 100644 --- a/Timeline.Tests/Helpers/ImageHelper.cs +++ b/Timeline.Tests/Helpers/ImageHelper.cs @@ -9,26 +9,18 @@ namespace Timeline.Tests.Helpers {
public static byte[] CreatePngWithSize(int width, int height)
{
- using (var image = new Image<Rgba32>(width, height))
- {
- using (var stream = new MemoryStream())
- {
- image.SaveAsPng(stream);
- return stream.ToArray();
- }
- }
+ using var image = new Image<Rgba32>(width, height);
+ using var stream = new MemoryStream();
+ image.SaveAsPng(stream);
+ return stream.ToArray();
}
public static byte[] CreateImageWithSize(int width, int height, IImageFormat format)
{
- using (var image = new Image<Rgba32>(width, height))
- {
- using (var stream = new MemoryStream())
- {
- image.Save(stream, format);
- return stream.ToArray();
- }
- }
+ using var image = new Image<Rgba32>(width, height);
+ using var stream = new MemoryStream();
+ image.Save(stream, format);
+ return stream.ToArray();
}
}
}
diff --git a/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs b/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs deleted file mode 100644 index af432095..00000000 --- a/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Net.Http;
-using System.Threading.Tasks;
-using Timeline.Models.Http;
-
-namespace Timeline.Tests.Helpers
-{
- public static class InvalidModelTestHelpers
- {
- public static async Task TestPostInvalidModel<T>(HttpClient client, string url, T body)
- {
- var response = await client.PostAsJsonAsync(url, body);
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.InvalidModel);
- }
-
- public static async Task TestPutInvalidModel<T>(HttpClient client, string url, T body)
- {
- var response = await client.PutAsJsonAsync(url, body);
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.InvalidModel);
- }
- }
-}
diff --git a/Timeline.Tests/Helpers/MyTestLoggerFactory.cs b/Timeline.Tests/Helpers/MyTestLoggerFactory.cs deleted file mode 100644 index b9960378..00000000 --- a/Timeline.Tests/Helpers/MyTestLoggerFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests.Helpers
-{
- public static class Logging
- {
- public static ILoggerFactory Create(ITestOutputHelper outputHelper)
- {
- // TODO: Use test output.
- return NullLoggerFactory.Instance;
- }
-
- public static IWebHostBuilder ConfigureTestLogging(this IWebHostBuilder builder)
- {
- builder.ConfigureLogging(logging =>
- {
- //logging.AddXunit(outputHelper);
- });
- return builder;
- }
- }
-}
diff --git a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs deleted file mode 100644 index dfbe6620..00000000 --- a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Data.Sqlite;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Diagnostics;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
-using Timeline.Entities;
-using Timeline.Services;
-using Timeline.Tests.Mock.Data;
-using Timeline.Tests.Mock.Services;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests.Helpers
-{
- public class MyWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
- {
- protected override void ConfigureWebHost(IWebHostBuilder builder)
- {
- builder.ConfigureTestServices(services =>
- {
- services.AddSingleton<IClock, TestClock>();
- });
- }
- }
-
- public static class WebApplicationFactoryExtensions
- {
- public static WebApplicationFactory<TEntry> WithTestConfig<TEntry>(this WebApplicationFactory<TEntry> factory, ITestOutputHelper outputHelper, out Action disposeAction) where TEntry : class
- {
- // We should keep the connection, so the database is persisted but not recreate every time.
- // See https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite#writing-tests .
- SqliteConnection _databaseConnection = new SqliteConnection("Data Source=:memory:;");
- _databaseConnection.Open();
-
- {
- var options = new DbContextOptionsBuilder<DatabaseContext>()
- .UseSqlite(_databaseConnection)
- .ConfigureWarnings(builder =>
- {
- builder.Throw(RelationalEventId.QueryClientEvaluationWarning);
- })
- .Options;
-
- using (var context = new DatabaseContext(options))
- {
- TestDatabase.InitDatabase(context);
- };
- }
-
- disposeAction = () =>
- {
- _databaseConnection.Close();
- _databaseConnection.Dispose();
- };
-
- return factory.WithWebHostBuilder(builder =>
- {
- builder.ConfigureTestLogging()
- .ConfigureServices(services =>
- {
- services.AddEntityFrameworkSqlite();
- services.AddDbContext<DatabaseContext>(options =>
- {
- options.UseSqlite(_databaseConnection);
- });
- });
- });
- }
- }
-}
diff --git a/Timeline.Tests/Helpers/TestApplication.cs b/Timeline.Tests/Helpers/TestApplication.cs new file mode 100644 index 00000000..b0187a30 --- /dev/null +++ b/Timeline.Tests/Helpers/TestApplication.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Data.Sqlite;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using Timeline.Entities;
+using Timeline.Tests.Mock.Data;
+
+namespace Timeline.Tests.Helpers
+{
+ public class TestApplication : IDisposable
+ {
+ public SqliteConnection DatabaseConnection { get; } = new SqliteConnection("Data Source=:memory:;");
+ public WebApplicationFactory<Startup> Factory { get; }
+
+ public TestApplication(WebApplicationFactory<Startup> factory)
+ {
+ // We should keep the connection, so the database is persisted but not recreate every time.
+ // See https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite#writing-tests .
+ DatabaseConnection.Open();
+
+ {
+ var options = new DbContextOptionsBuilder<DatabaseContext>()
+ .UseSqlite(DatabaseConnection)
+ .Options;
+
+ using (var context = new DatabaseContext(options))
+ {
+ TestDatabase.InitDatabase(context);
+ };
+ }
+
+ Factory = factory.WithWebHostBuilder(builder =>
+ {
+ builder.ConfigureServices(services =>
+ {
+ services.AddEntityFrameworkSqlite();
+ services.AddDbContext<DatabaseContext>(options =>
+ {
+ options.UseSqlite(DatabaseConnection);
+ });
+ });
+ });
+ }
+
+ public void Dispose()
+ {
+ DatabaseConnection.Close();
+ DatabaseConnection.Dispose();
+ }
+ }
+}
diff --git a/Timeline.Tests/IntegratedTests/AuthorizationTest.cs b/Timeline.Tests/IntegratedTests/AuthorizationTest.cs new file mode 100644 index 00000000..a31d98f5 --- /dev/null +++ b/Timeline.Tests/IntegratedTests/AuthorizationTest.cs @@ -0,0 +1,69 @@ +using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Timeline.Tests.Helpers;
+using Timeline.Tests.Helpers.Authentication;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class AuthorizationTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ {
+ private readonly TestApplication _testApp;
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public AuthorizationTest(WebApplicationFactory<Startup> factory)
+ {
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
+ }
+
+ public void Dispose()
+ {
+ _testApp.Dispose();
+ }
+
+ private const string BaseUrl = "testing/auth/";
+ private const string AuthorizeUrl = BaseUrl + "Authorize";
+ private const string UserUrl = BaseUrl + "User";
+ private const string AdminUrl = BaseUrl + "Admin";
+
+ [Fact]
+ public async Task UnauthenticationTest()
+ {
+ using var client = _factory.CreateDefaultClient();
+ var response = await client.GetAsync(AuthorizeUrl);
+ response.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
+ }
+
+ [Fact]
+ public async Task AuthenticationTest()
+ {
+ using var client = await _factory.CreateClientAsUser();
+ var response = await client.GetAsync(AuthorizeUrl);
+ response.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+
+ [Fact]
+ public async Task UserAuthorizationTest()
+ {
+ using var client = await _factory.CreateClientAsUser();
+ var response1 = await client.GetAsync(UserUrl);
+ response1.Should().HaveStatusCode(HttpStatusCode.OK);
+ var response2 = await client.GetAsync(AdminUrl);
+ response2.Should().HaveStatusCode(HttpStatusCode.Forbidden);
+ }
+
+ [Fact]
+ public async Task AdminAuthorizationTest()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var response1 = await client.GetAsync(UserUrl);
+ response1.Should().HaveStatusCode(HttpStatusCode.OK);
+ var response2 = await client.GetAsync(AdminUrl);
+ response2.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ }
+}
diff --git a/Timeline.Tests/IntegratedTests/TokenTest.cs b/Timeline.Tests/IntegratedTests/TokenTest.cs new file mode 100644 index 00000000..e9b6e1e9 --- /dev/null +++ b/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -0,0 +1,176 @@ +using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Timeline.Services;
+using Timeline.Tests.Helpers;
+using Timeline.Tests.Helpers.Authentication;
+using Timeline.Tests.Mock.Data;
+using Xunit;
+using static Timeline.ErrorCodes.Http.Token;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class TokenTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ {
+ private const string CreateTokenUrl = "token/create";
+ private const string VerifyTokenUrl = "token/verify";
+
+ private readonly TestApplication _testApp;
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public TokenTest(WebApplicationFactory<Startup> factory)
+ {
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
+ }
+
+ public void Dispose()
+ {
+ _testApp.Dispose();
+ }
+
+ public static IEnumerable<object[]> CreateToken_InvalidModel_Data()
+ {
+ yield return new[] { null, "p", null };
+ yield return new[] { "u", null, null };
+ yield return new object[] { "u", "p", 2000 };
+ yield return new object[] { "u", "p", -1 };
+ }
+
+ [Theory]
+ [MemberData(nameof(CreateToken_InvalidModel_Data))]
+ public async Task CreateToken_InvalidModel(string username, string password, int expire)
+ {
+ using var client = _factory.CreateDefaultClient();
+ (await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest
+ {
+ Username = username,
+ Password = password,
+ Expire = expire
+ })).Should().BeInvalidModel();
+ }
+
+ public static IEnumerable<object[]> CreateToken_UserCredential_Data()
+ {
+ yield return new[] { "usernotexist", "p" };
+ yield return new[] { MockUser.User.Username, "???" };
+ }
+
+ [Theory]
+ [MemberData(nameof(CreateToken_UserCredential_Data))]
+ public async void CreateToken_UserCredential(string username, string password)
+ {
+ using var client = _factory.CreateDefaultClient();
+ var response = await client.PostAsJsonAsync(CreateTokenUrl,
+ new CreateTokenRequest { Username = username, Password = password });
+ response.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Create.BadCredential);
+ }
+
+ [Fact]
+ public async Task CreateToken_Success()
+ {
+ using var client = _factory.CreateDefaultClient();
+ var response = await client.PostAsJsonAsync(CreateTokenUrl,
+ new CreateTokenRequest { Username = MockUser.User.Username, Password = MockUser.User.Password });
+ var body = response.Should().HaveStatusCode(200)
+ .And.Should().HaveJsonBody<CreateTokenResponse>().Which;
+ body.Token.Should().NotBeNullOrWhiteSpace();
+ body.User.Should().BeEquivalentTo(MockUser.User.Info);
+ }
+
+ [Fact]
+ public async Task VerifyToken_InvalidModel()
+ {
+ using var client = _factory.CreateDefaultClient();
+ (await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = null })).Should().BeInvalidModel();
+ }
+
+ [Fact]
+ public async Task VerifyToken_BadFormat()
+ {
+ using var client = _factory.CreateDefaultClient();
+ var response = await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = "bad token hahaha" });
+ response.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Verify.BadFormat);
+ }
+
+ [Fact]
+ public async Task VerifyToken_OldVersion()
+ {
+ using var client = _factory.CreateDefaultClient();
+ var token = (await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password)).Token;
+
+ using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
+ {
+ // create a user for test
+ var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
+ await userService.PatchUser(MockUser.User.Username, null, null);
+ }
+
+ (await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = token }))
+ .Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Verify.OldVersion);
+ }
+
+ [Fact]
+ public async Task VerifyToken_UserNotExist()
+ {
+ using var client = _factory.CreateDefaultClient();
+ var token = (await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password)).Token;
+
+ using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
+ {
+ var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
+ await userService.DeleteUser(MockUser.User.Username);
+ }
+
+ (await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = token }))
+ .Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Verify.UserNotExist);
+ }
+
+ //[Fact]
+ //public async Task VerifyToken_Expired()
+ //{
+ // using (var client = _factory.CreateDefaultClient())
+ // {
+ // // I can only control the token expired time but not current time
+ // // because verify logic is encapsuled in other library.
+ // var mockClock = _factory.GetTestClock();
+ // mockClock.MockCurrentTime = DateTime.Now - TimeSpan.FromDays(2);
+ // var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword, 1)).Token;
+ // var response = await client.PostAsJsonAsync(VerifyTokenUrl,
+ // new VerifyTokenRequest { Token = token });
+ // response.Should().HaveStatusCodeBadRequest()
+ // .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_Expired);
+ // mockClock.MockCurrentTime = null;
+ // }
+ //}
+
+ [Fact]
+ public async Task VerifyToken_Success()
+ {
+ using var client = _factory.CreateDefaultClient();
+ var createTokenResult = await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password);
+ var response = await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = createTokenResult.Token });
+ response.Should().HaveStatusCode(200)
+ .And.Should().HaveJsonBody<VerifyTokenResponse>()
+ .Which.User.Should().BeEquivalentTo(MockUser.User.Info);
+ }
+ }
+}
diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index 2a3442d1..ce389046 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -3,9 +3,9 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.Formats.Gif;
using System;
using System.Collections.Generic;
using System.IO;
@@ -14,31 +14,33 @@ using System.Net.Http; using System.Net.Http.Headers;
using System.Threading.Tasks;
using Timeline.Controllers;
-using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Helpers.Authentication;
using Xunit;
-using Xunit.Abstractions;
+using static Timeline.ErrorCodes.Http.Common;
+using static Timeline.ErrorCodes.Http.UserAvatar;
namespace Timeline.Tests.IntegratedTests
{
- public class UserAvatarUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
+ public class UserAvatarUnitTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
+ private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
- public UserAvatarUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ public UserAvatarUnitTest(WebApplicationFactory<Startup> factory)
{
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
}
public void Dispose()
{
- _disposeAction();
+ _testApp.Dispose();
}
[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
@@ -51,8 +53,9 @@ namespace Timeline.Tests.IntegratedTests {
{
var res = await client.GetAsync("users/usernotexist/avatar");
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Get_UserNotExist);
+ res.Should().HaveStatusCode(404)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Get.UserNotExist);
}
var env = _factory.Server.Host.Services.GetRequiredService<IWebHostEnvironment>();
@@ -61,7 +64,7 @@ namespace Timeline.Tests.IntegratedTests async Task GetReturnDefault(string username = "user")
{
var res = await client.GetAsync($"users/{username}/avatar");
- res.Should().HaveStatusCodeOk();
+ res.Should().HaveStatusCode(200);
res.Content.Headers.ContentType.MediaType.Should().Be("image/png");
var body = await res.Content.ReadAsByteArrayAsync();
body.Should().Equal(defaultAvatarData);
@@ -70,7 +73,7 @@ namespace Timeline.Tests.IntegratedTests EntityTagHeaderValue eTag;
{
var res = await client.GetAsync($"users/user/avatar");
- res.Should().HaveStatusCodeOk();
+ res.Should().HaveStatusCode(200);
res.Content.Headers.ContentType.MediaType.Should().Be("image/png");
var body = await res.Content.ReadAsByteArrayAsync();
body.Should().Equal(defaultAvatarData);
@@ -92,7 +95,7 @@ namespace Timeline.Tests.IntegratedTests request.Headers.TryAddWithoutValidation("If-None-Match", "\"dsdfd");
var res = await client.SendAsync(request);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_BadFormat_IfNonMatch);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch);
}
{
@@ -122,7 +125,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_Missing_ContentLength);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.Missing_ContentLength);
}
{
@@ -130,7 +133,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentLength = 1;
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_Missing_ContentType);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.Missing_ContentType);
}
{
@@ -139,7 +142,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_Zero_ContentLength);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.Zero_ContentLength);
}
{
@@ -153,7 +156,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Content_TooBig);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Content.TooBig);
}
{
@@ -162,7 +165,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Content_UnmatchedLength_Less);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Content.UnmatchedLength_Smaller);
}
{
@@ -171,25 +174,25 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Content_UnmatchedLength_Bigger);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Content.UnmatchedLength_Bigger);
}
{
var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/png");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_BadFormat_CantDecode);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_CantDecode);
}
{
var res = await client.PutByteArrayAsync("users/user/avatar", mockAvatar.Data, "image/jpeg");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_BadFormat_UnmatchedFormat);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_UnmatchedFormat);
}
{
var res = await client.PutByteArrayAsync("users/user/avatar", ImageHelper.CreatePngWithSize(100, 200), "image/png");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_BadFormat_BadSize);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_BadSize);
}
{
@@ -197,7 +200,7 @@ namespace Timeline.Tests.IntegratedTests res.Should().HaveStatusCode(HttpStatusCode.OK);
var res2 = await client.GetAsync("users/user/avatar");
- res2.Should().HaveStatusCodeOk();
+ res2.Should().HaveStatusCode(200);
res2.Content.Headers.ContentType.MediaType.Should().Be(mockAvatar.Type);
var body = await res2.Content.ReadAsByteArrayAsync();
body.Should().Equal(mockAvatar.Data);
@@ -219,19 +222,19 @@ namespace Timeline.Tests.IntegratedTests {
var res = await client.PutByteArrayAsync("users/admin/avatar", new[] { (byte)0x00 }, "image/png");
res.Should().HaveStatusCode(HttpStatusCode.Forbidden)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Forbid);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.Forbid);
}
{
var res = await client.DeleteAsync("users/admin/avatar");
res.Should().HaveStatusCode(HttpStatusCode.Forbidden)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Delete_Forbid);
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Delete.Forbid);
}
for (int i = 0; i < 2; i++) // double delete should work.
{
var res = await client.DeleteAsync("users/user/avatar");
- res.Should().HaveStatusCodeOk();
+ res.Should().HaveStatusCode(200);
await GetReturnDefault();
}
}
@@ -251,14 +254,34 @@ namespace Timeline.Tests.IntegratedTests {
var res = await client.PutByteArrayAsync("users/usernotexist/avatar", new[] { (byte)0x00 }, "image/png");
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_UserNotExist);
+ res.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Put.UserNotExist);
}
{
var res = await client.DeleteAsync("users/usernotexist/avatar");
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Delete_UserNotExist);
+ res.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody().Which.Code.Should().Be(Delete.UserNotExist);
+ }
+ }
+
+ // bad username check
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ {
+ var res = await client.GetAsync("users/u!ser/avatar");
+ res.Should().BeInvalidModel();
+ }
+
+ {
+ var res = await client.PutByteArrayAsync("users/u!ser/avatar", ImageHelper.CreatePngWithSize(100, 100), "image/png");
+ res.Should().BeInvalidModel();
+ }
+
+ {
+ var res = await client.DeleteAsync("users/u!ser/avatar");
+ res.Should().BeInvalidModel();
}
}
}
diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs deleted file mode 100644 index ba15b7ca..00000000 --- a/Timeline.Tests/IntegratedTests/UserDetailTest.cs +++ /dev/null @@ -1,146 +0,0 @@ -using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using System;
-using System.Net;
-using System.Threading.Tasks;
-using Timeline.Controllers;
-using Timeline.Models;
-using Timeline.Models.Http;
-using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Timeline.Tests.Mock.Data;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests.IntegratedTests
-{
- public class UserDetailTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
- {
- private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
-
- public UserDetailTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
- {
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
- }
-
- public void Dispose()
- {
- _disposeAction();
- }
-
- [Fact]
- public async Task TestAsUser()
- {
- using (var client = await _factory.CreateClientAsUser())
- {
- {
- var res = await client.GetAsync($"users/usernotexist/nickname");
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.GetNickname_UserNotExist);
- }
-
- {
- var res = await client.GetAsync($"users/usernotexist/details");
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.Get_UserNotExist);
- }
-
- async Task GetAndTest(UserDetail d)
- {
- var res = await client.GetAsync($"users/{MockUsers.UserUsername}/details");
- res.Should().HaveStatusCodeOk()
- .And.Should().HaveBodyAsJson<UserDetail>()
- .Which.Should().BeEquivalentTo(d);
- }
-
- await GetAndTest(new UserDetail());
-
- {
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.AdminUsername}/details", new UserDetail());
- res.Should().HaveStatusCode(HttpStatusCode.Forbidden)
- .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.Patch_Forbid);
- }
-
- {
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", new UserDetail
- {
- Nickname = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- QQ = "aaaaaaa",
- Email = "aaaaaa",
- PhoneNumber = "aaaaaaaa"
- });
- var body = res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveBodyAsCommonResponse().Which;
- body.Code.Should().Be(CommonResponse.ErrorCodes.InvalidModel);
- foreach (var key in new string[] { "nickname", "qq", "email", "phonenumber" })
- {
- body.Message.Should().ContainEquivalentOf(key);
- }
- }
-
-
- var detail = new UserDetail
- {
- Nickname = "aaa",
- QQ = "1234567",
- Email = "aaaa@aaa.net",
- Description = "aaaaaaaaa"
- };
-
- {
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", detail);
- res.Should().HaveStatusCodeOk();
- await GetAndTest(detail);
- }
-
- {
- var res = await client.GetAsync($"users/{MockUsers.UserUsername}/nickname");
- res.Should().HaveStatusCodeOk().And.Should().HaveBodyAsJson<UserDetail>()
- .Which.Should().BeEquivalentTo(new UserDetail
- {
- Nickname = detail.Nickname
- });
- }
-
- var detail2 = new UserDetail
- {
- QQ = "",
- PhoneNumber = "12345678910",
- Description = "bbbbbbbb"
- };
-
- {
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", detail2);
- res.Should().HaveStatusCodeOk();
- await GetAndTest(new UserDetail
- {
- Nickname = detail.Nickname,
- QQ = null,
- Email = detail.Email,
- PhoneNumber = detail2.PhoneNumber,
- Description = detail2.Description
- });
- }
- }
- }
-
- [Fact]
- public async Task TestAsAdmin()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- {
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", new UserDetail());
- res.Should().HaveStatusCodeOk();
- }
-
- {
- var res = await client.PatchAsJsonAsync($"users/usernotexist/details", new UserDetail());
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserDetailController.ErrorCodes.Patch_UserNotExist);
- }
- }
- }
- }
-}
\ No newline at end of file diff --git a/Timeline.Tests/IntegratedTests/UserTest.cs b/Timeline.Tests/IntegratedTests/UserTest.cs new file mode 100644 index 00000000..ec70b7e8 --- /dev/null +++ b/Timeline.Tests/IntegratedTests/UserTest.cs @@ -0,0 +1,277 @@ +using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Timeline.Models;
+using Timeline.Models.Http;
+using Timeline.Tests.Helpers;
+using Timeline.Tests.Helpers.Authentication;
+using Timeline.Tests.Mock.Data;
+using Xunit;
+using static Timeline.ErrorCodes.Http.User;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class UserTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ {
+ private readonly TestApplication _testApp;
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public UserTest(WebApplicationFactory<Startup> factory)
+ {
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
+ }
+
+ public void Dispose()
+ {
+ _testApp.Dispose();
+ }
+
+ [Fact]
+ public async Task Get_List_Success()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.GetAsync("users");
+ res.Should().HaveStatusCode(200)
+ .And.Should().HaveJsonBody<UserInfo[]>()
+ .Which.Should().BeEquivalentTo(MockUser.UserInfoList);
+ }
+
+ [Fact]
+ public async Task Get_Single_Success()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.GetAsync("users/" + MockUser.User.Username);
+ res.Should().HaveStatusCode(200)
+ .And.Should().HaveJsonBody<UserInfo>()
+ .Which.Should().BeEquivalentTo(MockUser.User.Info);
+ }
+
+ [Fact]
+ public async Task Get_InvalidModel()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.GetAsync("users/aaa!a");
+ res.Should().BeInvalidModel();
+ }
+
+ [Fact]
+ public async Task Get_Users_404()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.GetAsync("users/usernotexist");
+ res.Should().HaveStatusCode(404)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Get.NotExist);
+ }
+
+ public static IEnumerable<object[]> Put_InvalidModel_Data()
+ {
+ yield return new object[] { "aaa", null, false };
+ yield return new object[] { "aaa", "p", null };
+ yield return new object[] { "aa!a", "p", false };
+ }
+
+ [Theory]
+ [MemberData(nameof(Put_InvalidModel_Data))]
+ public async Task Put_InvalidModel(string username, string password, bool? administrator)
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ (await client.PutAsJsonAsync("users/" + username,
+ new UserPutRequest { Password = password, Administrator = administrator }))
+ .Should().BeInvalidModel();
+ }
+
+ private async Task CheckAdministrator(HttpClient client, string username, bool administrator)
+ {
+ var res = await client.GetAsync("users/" + username);
+ res.Should().HaveStatusCode(200)
+ .And.Should().HaveJsonBody<UserInfo>()
+ .Which.Administrator.Should().Be(administrator);
+ }
+
+ [Fact]
+ public async Task Put_Modiefied()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.PutAsJsonAsync("users/" + MockUser.User.Username, new UserPutRequest
+ {
+ Password = "password",
+ Administrator = false
+ });
+ res.Should().BePut(false);
+ await CheckAdministrator(client, MockUser.User.Username, false);
+ }
+
+ [Fact]
+ public async Task Put_Created()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ const string username = "puttest";
+ const string url = "users/" + username;
+
+ var res = await client.PutAsJsonAsync(url, new UserPutRequest
+ {
+ Password = "password",
+ Administrator = false
+ });
+ res.Should().BePut(true);
+ await CheckAdministrator(client, username, false);
+ }
+
+ [Fact]
+ public async Task Patch_NotExist()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
+ res.Should().HaveStatusCode(404)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Patch.NotExist);
+ }
+
+ [Fact]
+ public async Task Patch_InvalidModel()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.PatchAsJsonAsync("users/aaa!a", new UserPatchRequest { });
+ res.Should().BeInvalidModel();
+ }
+
+ [Fact]
+ public async Task Patch_Success()
+ {
+ using var client = await _factory.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);
+ }
+ }
+
+ [Fact]
+ public async Task Delete_InvalidModel()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var url = "users/aaa!a";
+ var res = await client.DeleteAsync(url);
+ res.Should().BeInvalidModel();
+ }
+
+ [Fact]
+ public async Task Delete_Deleted()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var url = "users/" + MockUser.User.Username;
+ var res = await client.DeleteAsync(url);
+ res.Should().BeDelete(true);
+
+ var res2 = await client.GetAsync(url);
+ res2.Should().HaveStatusCode(404);
+ }
+
+ [Fact]
+ public async Task Delete_NotExist()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.DeleteAsync("users/usernotexist");
+ res.Should().BeDelete(false);
+ }
+
+ private const string changeUsernameUrl = "userop/changeusername";
+
+ public static IEnumerable<object[]> Op_ChangeUsername_InvalidModel_Data()
+ {
+ yield return new[] { null, "uuu" };
+ yield return new[] { "uuu", null };
+ yield return new[] { "a!a", "uuu" };
+ yield return new[] { "uuu", "a!a" };
+ }
+
+ [Theory]
+ [MemberData(nameof(Op_ChangeUsername_InvalidModel_Data))]
+ public async Task Op_ChangeUsername_InvalidModel(string oldUsername, string newUsername)
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ (await client.PostAsJsonAsync(changeUsernameUrl,
+ new ChangeUsernameRequest { OldUsername = oldUsername, NewUsername = newUsername }))
+ .Should().BeInvalidModel();
+ }
+
+ [Fact]
+ public async Task Op_ChangeUsername_UserNotExist()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.PostAsJsonAsync(changeUsernameUrl,
+ new ChangeUsernameRequest { OldUsername = "usernotexist", NewUsername = "newUsername" });
+ res.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Op.ChangeUsername.NotExist);
+ }
+
+ [Fact]
+ public async Task Op_ChangeUsername_UserAlreadyExist()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ var res = await client.PostAsJsonAsync(changeUsernameUrl,
+ new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = MockUser.Admin.Username });
+ res.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Op.ChangeUsername.AlreadyExist);
+ }
+
+ [Fact]
+ public async Task Op_ChangeUsername_Success()
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ const string newUsername = "hahaha";
+ var res = await client.PostAsJsonAsync(changeUsernameUrl,
+ new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = newUsername });
+ res.Should().HaveStatusCode(200);
+ await client.CreateUserTokenAsync(newUsername, MockUser.User.Password);
+ }
+
+ private const string changePasswordUrl = "userop/changepassword";
+
+ public static IEnumerable<object[]> Op_ChangePassword_InvalidModel_Data()
+ {
+ yield return new[] { null, "ppp" };
+ yield return new[] { "ppp", null };
+ }
+
+ [Theory]
+ [MemberData(nameof(Op_ChangePassword_InvalidModel_Data))]
+ public async Task Op_ChangePassword_InvalidModel(string oldPassword, string newPassword)
+ {
+ using var client = await _factory.CreateClientAsUser();
+ (await client.PostAsJsonAsync(changePasswordUrl,
+ new ChangePasswordRequest { OldPassword = oldPassword, NewPassword = newPassword }))
+ .Should().BeInvalidModel();
+ }
+
+ [Fact]
+ public async Task Op_ChangePassword_BadOldPassword()
+ {
+ using var client = await _factory.CreateClientAsUser();
+ var res = await client.PostAsJsonAsync(changePasswordUrl, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" });
+ res.Should().HaveStatusCode(400)
+ .And.Should().HaveCommonBody()
+ .Which.Code.Should().Be(Op.ChangePassword.BadOldPassword);
+ }
+
+ [Fact]
+ public async Task Op_ChangePassword_Success()
+ {
+ using var client = await _factory.CreateClientAsUser();
+ const string newPassword = "new";
+ var res = await client.PostAsJsonAsync(changePasswordUrl,
+ new ChangePasswordRequest { OldPassword = MockUser.User.Password, NewPassword = newPassword });
+ res.Should().HaveStatusCode(200);
+ await _factory.CreateDefaultClient() // don't use client above, because it sets authorization header
+ .CreateUserTokenAsync(MockUser.User.Username, newPassword);
+ }
+ }
+}
diff --git a/Timeline.Tests/Mock/Data/TestDatabase.cs b/Timeline.Tests/Mock/Data/TestDatabase.cs index dd04f8f9..1e662546 100644 --- a/Timeline.Tests/Mock/Data/TestDatabase.cs +++ b/Timeline.Tests/Mock/Data/TestDatabase.cs @@ -10,36 +10,33 @@ namespace Timeline.Tests.Mock.Data public static void InitDatabase(DatabaseContext context)
{
context.Database.EnsureCreated();
- context.Users.AddRange(MockUsers.CreateMockUsers());
+ context.Users.AddRange(MockUser.CreateMockEntities());
context.SaveChanges();
}
- private readonly SqliteConnection _databaseConnection;
- private readonly DatabaseContext _databaseContext;
-
public TestDatabase()
{
- _databaseConnection = new SqliteConnection("Data Source=:memory:;");
- _databaseConnection.Open();
+ DatabaseConnection = new SqliteConnection("Data Source=:memory:;");
+ DatabaseConnection.Open();
var options = new DbContextOptionsBuilder<DatabaseContext>()
- .UseSqlite(_databaseConnection)
+ .UseSqlite(DatabaseConnection)
.Options;
- _databaseContext = new DatabaseContext(options);
+ DatabaseContext = new DatabaseContext(options);
- InitDatabase(_databaseContext);
+ InitDatabase(DatabaseContext);
}
public void Dispose()
{
- _databaseContext.Dispose();
+ DatabaseContext.Dispose();
- _databaseConnection.Close();
- _databaseConnection.Dispose();
+ DatabaseConnection.Close();
+ DatabaseConnection.Dispose();
}
- public SqliteConnection DatabaseConnection => _databaseConnection;
- public DatabaseContext DatabaseContext => _databaseContext;
+ public SqliteConnection DatabaseConnection { get; }
+ public DatabaseContext DatabaseContext { get; }
}
}
diff --git a/Timeline.Tests/Mock/Data/TestUsers.cs b/Timeline.Tests/Mock/Data/TestUsers.cs index 378fc280..fa75236a 100644 --- a/Timeline.Tests/Mock/Data/TestUsers.cs +++ b/Timeline.Tests/Mock/Data/TestUsers.cs @@ -1,52 +1,50 @@ using System;
using System.Collections.Generic;
-using System.Linq;
using Timeline.Entities;
using Timeline.Models;
using Timeline.Services;
namespace Timeline.Tests.Mock.Data
{
- public static class MockUsers
+ public class MockUser
{
- static MockUsers()
+ public MockUser(string username, string password, bool administrator)
{
- var mockUserInfos = CreateMockUsers().Select(u => UserUtility.CreateUserInfo(u)).ToList();
- UserUserInfo = mockUserInfos[0];
- AdminUserInfo = mockUserInfos[1];
- UserInfos = mockUserInfos;
+ Info = new UserInfo(username, administrator);
+ Password = password;
}
- public const string UserUsername = "user";
- public const string AdminUsername = "admin";
- public const string UserPassword = "user";
- public const string AdminPassword = "admin";
+ public UserInfo Info { get; set; }
+ public string Username => Info.Username;
+ public string Password { get; set; }
+ public bool Administrator => Info.Administrator;
- // emmmmmmm. Never reuse the user instances because EF Core uses them which will cause strange things.
- internal static IEnumerable<User> CreateMockUsers()
+
+ public static MockUser User { get; } = new MockUser("user", "userpassword", false);
+ public static MockUser Admin { get; } = new MockUser("admin", "adminpassword", true);
+
+ public static IReadOnlyList<UserInfo> UserInfoList { get; } = new List<UserInfo> { User.Info, Admin.Info };
+
+ // emmmmmmm. Never reuse the user instances because EF Core uses them, which will cause strange things.
+ public static IEnumerable<User> CreateMockEntities()
{
- var users = new List<User>();
var passwordService = new PasswordService();
- users.Add(new User
+ User Create(MockUser user)
{
- Name = UserUsername,
- EncryptedPassword = passwordService.HashPassword(UserPassword),
- RoleString = UserUtility.IsAdminToRoleString(false),
- Avatar = UserAvatar.Create(DateTime.Now)
- });
- users.Add(new User
+ return new User
+ {
+ Name = user.Username,
+ EncryptedPassword = passwordService.HashPassword(user.Password),
+ RoleString = UserRoleConvert.ToString(user.Administrator),
+ Avatar = null
+ };
+ }
+
+ return new List<User>
{
- Name = AdminUsername,
- EncryptedPassword = passwordService.HashPassword(AdminPassword),
- RoleString = UserUtility.IsAdminToRoleString(true),
- Avatar = UserAvatar.Create(DateTime.Now)
- });
- return users;
+ Create(User),
+ Create(Admin)
+ };
}
-
- public static IReadOnlyList<UserInfo> UserInfos { get; }
-
- public static UserInfo AdminUserInfo { get; }
- public static UserInfo UserUserInfo { get; }
}
}
diff --git a/Timeline.Tests/Mock/Services/TestClock.cs b/Timeline.Tests/Mock/Services/TestClock.cs index 0082171e..6671395a 100644 --- a/Timeline.Tests/Mock/Services/TestClock.cs +++ b/Timeline.Tests/Mock/Services/TestClock.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.Extensions.DependencyInjection;
using System;
using Timeline.Services;
@@ -14,12 +12,4 @@ namespace Timeline.Tests.Mock.Services return MockCurrentTime.GetValueOrDefault(DateTime.Now);
}
}
-
- public static class TestClockWebApplicationFactoryExtensions
- {
- public static TestClock GetTestClock<T>(this WebApplicationFactory<T> factory) where T : class
- {
- return factory.Server.Host.Services.GetRequiredService<IClock>() as TestClock;
- }
- }
}
diff --git a/Timeline.Tests/Mock/Services/TestStringLocalizerFactory.cs b/Timeline.Tests/Mock/Services/TestStringLocalizerFactory.cs new file mode 100644 index 00000000..4084dd8f --- /dev/null +++ b/Timeline.Tests/Mock/Services/TestStringLocalizerFactory.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace Timeline.Tests.Mock.Services
+{
+ internal static class TestStringLocalizerFactory
+ {
+ internal static IStringLocalizerFactory Create()
+ {
+ return new ResourceManagerStringLocalizerFactory(
+ Options.Create(new LocalizationOptions()
+ {
+ ResourcesPath = "Resource"
+ }),
+ NullLoggerFactory.Instance
+ );
+ }
+
+ internal static IStringLocalizer<T> Create<T>(this IStringLocalizerFactory factory)
+ {
+ return new StringLocalizer<T>(factory);
+ }
+ }
+}
diff --git a/Timeline.Tests/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj index 1852da5f..497a00b7 100644 --- a/Timeline.Tests/Timeline.Tests.csproj +++ b/Timeline.Tests/Timeline.Tests.csproj @@ -1,27 +1,34 @@ <Project Sdk="Microsoft.NET.Sdk.Web">
- <PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
- </PropertyGroup>
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
- <ItemGroup>
- <PackageReference Include="coverlet.collector" Version="1.1.0">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="FluentAssertions" Version="5.9.0" />
- <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
- </PackageReference>
- </ItemGroup>
+ <LangVersion>8.0</LangVersion>
+ </PropertyGroup>
- <ItemGroup>
- <ProjectReference Include="..\Timeline\Timeline.csproj" />
- </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="coverlet.collector" Version="1.1.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="FluentAssertions" Version="5.9.0" />
+ <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.6">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
+ <PackageReference Include="Moq" Version="4.13.1" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Timeline\Timeline.csproj" />
+ </ItemGroup>
</Project>
diff --git a/Timeline.Tests/TokenUnitTest.cs b/Timeline.Tests/TokenUnitTest.cs deleted file mode 100644 index 3babacf7..00000000 --- a/Timeline.Tests/TokenUnitTest.cs +++ /dev/null @@ -1,190 +0,0 @@ -using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Net.Http;
-using Timeline.Controllers;
-using Timeline.Models.Http;
-using Timeline.Services;
-using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Timeline.Tests.Mock.Data;
-using Timeline.Tests.Mock.Services;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests
-{
- public class TokenUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
- {
- private const string CreateTokenUrl = "token/create";
- private const string VerifyTokenUrl = "token/verify";
-
- private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
-
- public TokenUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
- {
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
- }
-
- public void Dispose()
- {
- _disposeAction();
- }
-
- [Fact]
- public async void CreateToken_InvalidModel()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- // missing username
- await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
- new CreateTokenRequest { Username = null, Password = "user" });
- // missing password
- await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
- new CreateTokenRequest { Username = "user", Password = null });
- // bad expire offset
- await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
- new CreateTokenRequest
- {
- Username = MockUsers.UserUsername,
- Password = MockUsers.UserPassword,
- ExpireOffset = -1000
- });
- }
- }
-
- [Fact]
- public async void CreateToken_UserNotExist()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var response = await client.PostAsJsonAsync(CreateTokenUrl,
- new CreateTokenRequest { Username = "usernotexist", Password = "???" });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Create_UserNotExist);
- }
- }
-
- [Fact]
- public async void CreateToken_BadPassword()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var response = await client.PostAsJsonAsync(CreateTokenUrl,
- new CreateTokenRequest { Username = MockUsers.UserUsername, Password = "???" });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Create_BadPassword);
- }
- }
-
- [Fact]
- public async void CreateToken_Success()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var response = await client.PostAsJsonAsync(CreateTokenUrl,
- new CreateTokenRequest { Username = MockUsers.UserUsername, Password = MockUsers.UserPassword });
- var body = response.Should().HaveStatusCodeOk()
- .And.Should().HaveBodyAsJson<CreateTokenResponse>().Which;
- body.Token.Should().NotBeNullOrWhiteSpace();
- body.User.Should().BeEquivalentTo(MockUsers.UserUserInfo);
- }
- }
-
- [Fact]
- public async void VerifyToken_InvalidModel()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- // missing token
- await InvalidModelTestHelpers.TestPostInvalidModel(client, VerifyTokenUrl,
- new VerifyTokenRequest { Token = null });
- }
- }
-
- [Fact]
- public async void VerifyToken_BadToken()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = "bad token hahaha" });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_BadToken);
- }
- }
-
- [Fact]
- public async void VerifyToken_BadVersion()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword)).Token;
-
- using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
- {
- // create a user for test
- var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
- await userService.PatchUser(MockUsers.UserUsername, null, null);
- }
-
- var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_BadVersion);
- }
- }
-
- [Fact]
- public async void VerifyToken_UserNotExist()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword)).Token;
-
- using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
- {
- // create a user for test
- var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
- await userService.DeleteUser(MockUsers.UserUsername);
- }
-
- var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_UserNotExist);
- }
- }
-
- [Fact]
- public async void VerifyToken_Expired()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- // I can only control the token expired time but not current time
- // because verify logic is encapsuled in other library.
- var mockClock = _factory.GetTestClock();
- mockClock.MockCurrentTime = DateTime.Now - TimeSpan.FromDays(2);
- var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword, 1)).Token;
- var response = await client.PostAsJsonAsync(VerifyTokenUrl,
- new VerifyTokenRequest { Token = token });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_Expired);
- mockClock.MockCurrentTime = null;
- }
- }
-
- [Fact]
- public async void VerifyToken_Success()
- {
- using (var client = _factory.CreateDefaultClient())
- {
- var createTokenResult = await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword);
- var response = await client.PostAsJsonAsync(VerifyTokenUrl,
- new VerifyTokenRequest { Token = createTokenResult.Token });
- response.Should().HaveStatusCodeOk()
- .And.Should().HaveBodyAsJson<VerifyTokenResponse>()
- .Which.User.Should().BeEquivalentTo(MockUsers.UserUserInfo);
- }
- }
- }
-}
diff --git a/Timeline.Tests/UserAvatarServiceTest.cs b/Timeline.Tests/UserAvatarServiceTest.cs index 93bb70ae..1f71f6f6 100644 --- a/Timeline.Tests/UserAvatarServiceTest.cs +++ b/Timeline.Tests/UserAvatarServiceTest.cs @@ -1,8 +1,11 @@ using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+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;
@@ -10,40 +13,12 @@ using Timeline.Entities; using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Mock.Data;
+using Timeline.Tests.Mock.Services;
using Xunit;
using Xunit.Abstractions;
namespace Timeline.Tests
{
- public class MockDefaultUserAvatarProvider : IDefaultUserAvatarProvider
- {
- public static string ETag { get; } = "Hahaha";
-
- public static AvatarInfo AvatarInfo { get; } = new AvatarInfo
- {
- Avatar = new Avatar { Type = "image/test", Data = Encoding.ASCII.GetBytes("test") },
- LastModified = DateTime.Now
- };
-
- public Task<string> GetDefaultAvatarETag()
- {
- return Task.FromResult(ETag);
- }
-
- public Task<AvatarInfo> GetDefaultAvatar()
- {
- return Task.FromResult(AvatarInfo);
- }
- }
-
- public class MockUserAvatarValidator : IUserAvatarValidator
- {
- public Task Validate(Avatar avatar)
- {
- return Task.CompletedTask;
- }
- }
-
public class UserAvatarValidatorTest : IClassFixture<UserAvatarValidator>
{
private readonly UserAvatarValidator _validator;
@@ -62,8 +37,8 @@ namespace Timeline.Tests Type = "image/png"
};
_validator.Awaiting(v => v.Validate(avatar))
- .Should().Throw<AvatarDataException>()
- .Where(e => e.Avatar == avatar && e.Error == AvatarDataException.ErrorReason.CantDecode);
+ .Should().Throw<AvatarFormatException>()
+ .Where(e => e.Avatar == avatar && e.Error == AvatarFormatException.ErrorReason.CantDecode);
}
[Fact]
@@ -75,8 +50,8 @@ namespace Timeline.Tests Type = "image/jpeg"
};
_validator.Awaiting(v => v.Validate(avatar))
- .Should().Throw<AvatarDataException>()
- .Where(e => e.Avatar == avatar && e.Error == AvatarDataException.ErrorReason.UnmatchedFormat);
+ .Should().Throw<AvatarFormatException>()
+ .Where(e => e.Avatar == avatar && e.Error == AvatarFormatException.ErrorReason.UnmatchedFormat);
}
[Fact]
@@ -88,8 +63,8 @@ namespace Timeline.Tests Type = PngFormat.Instance.DefaultMimeType
};
_validator.Awaiting(v => v.Validate(avatar))
- .Should().Throw<AvatarDataException>()
- .Where(e => e.Avatar == avatar && e.Error == AvatarDataException.ErrorReason.BadSize);
+ .Should().Throw<AvatarFormatException>()
+ .Where(e => e.Avatar == avatar && e.Error == AvatarFormatException.ErrorReason.BadSize);
}
[Fact]
@@ -105,25 +80,33 @@ namespace Timeline.Tests }
}
- public class UserAvatarServiceTest : IDisposable, IClassFixture<MockDefaultUserAvatarProvider>, IClassFixture<MockUserAvatarValidator>
+ public class UserAvatarServiceTest : IDisposable
{
- private UserAvatar MockAvatarEntity1 { get; } = new UserAvatar
+ private UserAvatar CreateMockAvatarEntity(string key) => new UserAvatar
+ {
+ Type = $"image/test{key}",
+ Data = Encoding.ASCII.GetBytes($"mock{key}"),
+ ETag = $"etag{key}",
+ LastModified = DateTime.Now
+ };
+
+ private AvatarInfo CreateMockAvatarInfo(string key) => new AvatarInfo
{
- Type = "image/testaaa",
- Data = Encoding.ASCII.GetBytes("amock"),
- ETag = "aaaa",
+ Avatar = new Avatar
+ {
+ Type = $"image/test{key}",
+ Data = Encoding.ASCII.GetBytes($"mock{key}")
+ },
LastModified = DateTime.Now
};
- private UserAvatar MockAvatarEntity2 { get; } = new UserAvatar
+ private Avatar CreateMockAvatar(string key) => new Avatar
{
- Type = "image/testbbb",
- Data = Encoding.ASCII.GetBytes("bmock"),
- ETag = "bbbb",
- LastModified = DateTime.Now + TimeSpan.FromMinutes(1)
+ Type = $"image/test{key}",
+ Data = Encoding.ASCII.GetBytes($"mock{key}")
};
- private Avatar ToAvatar(UserAvatar entity)
+ private static Avatar ToAvatar(UserAvatar entity)
{
return new Avatar
{
@@ -132,7 +115,7 @@ namespace Timeline.Tests };
}
- private AvatarInfo ToAvatarInfo(UserAvatar entity)
+ private static AvatarInfo ToAvatarInfo(UserAvatar entity)
{
return new AvatarInfo
{
@@ -141,177 +124,161 @@ namespace Timeline.Tests };
}
- private void Set(UserAvatar to, UserAvatar from)
- {
- to.Type = from.Type;
- to.Data = from.Data;
- to.ETag = from.ETag;
- to.LastModified = from.LastModified;
- }
-
- private readonly MockDefaultUserAvatarProvider _mockDefaultUserAvatarProvider;
+ private readonly Mock<IDefaultUserAvatarProvider> _mockDefaultAvatarProvider;
+ private readonly Mock<IUserAvatarValidator> _mockValidator;
+ private readonly Mock<IETagGenerator> _mockETagGenerator;
+ private readonly Mock<IClock> _mockClock;
- private readonly ILoggerFactory _loggerFactory;
private readonly TestDatabase _database;
- private readonly IETagGenerator _eTagGenerator;
-
private readonly UserAvatarService _service;
- public UserAvatarServiceTest(ITestOutputHelper outputHelper, MockDefaultUserAvatarProvider mockDefaultUserAvatarProvider, MockUserAvatarValidator mockUserAvatarValidator)
+ public UserAvatarServiceTest()
{
- _mockDefaultUserAvatarProvider = mockDefaultUserAvatarProvider;
+ _mockDefaultAvatarProvider = new Mock<IDefaultUserAvatarProvider>();
+ _mockValidator = new Mock<IUserAvatarValidator>();
+ _mockETagGenerator = new Mock<IETagGenerator>();
+ _mockClock = new Mock<IClock>();
- _loggerFactory = Logging.Create(outputHelper);
_database = new TestDatabase();
- _eTagGenerator = new ETagGenerator();
-
- _service = new UserAvatarService(_loggerFactory.CreateLogger<UserAvatarService>(), _database.DatabaseContext, _mockDefaultUserAvatarProvider, mockUserAvatarValidator, _eTagGenerator);
+ _service = new UserAvatarService(NullLogger<UserAvatarService>.Instance, _database.DatabaseContext, _mockDefaultAvatarProvider.Object, _mockValidator.Object, _mockETagGenerator.Object, _mockClock.Object);
}
public void Dispose()
{
- _loggerFactory.Dispose();
_database.Dispose();
}
- [Fact]
- public void GetAvatarETag_ShouldThrow_ArgumentException()
- {
- // no need to await because arguments are checked syncronizedly.
- _service.Invoking(s => s.GetAvatarETag(null)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.GetAvatarETag("")).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
- }
-
- [Fact]
- public void GetAvatarETag_ShouldThrow_UserNotExistException()
+ [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)
{
- const string username = "usernotexist";
- _service.Awaiting(s => s.GetAvatarETag(username)).Should().Throw<UserNotExistException>()
- .Where(e => e.Username == username);
+ await _service.Awaiting(s => s.GetAvatarETag(username)).Should().ThrowAsync(exceptionType);
}
[Fact]
public async Task GetAvatarETag_ShouldReturn_Default()
{
- const string username = MockUsers.UserUsername;
- (await _service.GetAvatarETag(username)).Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatarETag()));
+ 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()
{
- const string username = MockUsers.UserUsername;
+ string username = MockUser.User.Username;
+ var mockAvatarEntity = CreateMockAvatarEntity("aaa");
{
- // create mock data
var context = _database.DatabaseContext;
var user = await context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync();
- Set(user.Avatar, MockAvatarEntity1);
+ user.Avatar = mockAvatarEntity;
await context.SaveChangesAsync();
}
-
- (await _service.GetAvatarETag(username)).Should().BeEquivalentTo(MockAvatarEntity1.ETag);
+ (await _service.GetAvatarETag(username)).Should().BeEquivalentTo(mockAvatarEntity.ETag);
}
- [Fact]
- public void GetAvatar_ShouldThrow_ArgumentException()
+ [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)
{
- // no need to await because arguments are checked syncronizedly.
- _service.Invoking(s => s.GetAvatar(null)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.GetAvatar("")).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
- }
+ await _service.Awaiting(s => s.GetAvatar(username)).Should().ThrowAsync(exceptionType);
- [Fact]
- public void GetAvatar_ShouldThrow_UserNotExistException()
- {
- const string username = "usernotexist";
- _service.Awaiting(s => s.GetAvatar(username)).Should().Throw<UserNotExistException>()
- .Where(e => e.Username == username);
}
[Fact]
public async Task GetAvatar_ShouldReturn_Default()
{
- const string username = MockUsers.UserUsername;
- (await _service.GetAvatar(username)).Avatar.Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatar()).Avatar);
+ 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()
{
- const string username = MockUsers.UserUsername;
-
+ string username = MockUser.User.Username;
+ var mockAvatarEntity = CreateMockAvatarEntity("aaa");
{
- // create mock data
var context = _database.DatabaseContext;
var user = await context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync();
- Set(user.Avatar, MockAvatarEntity1);
+ user.Avatar = mockAvatarEntity;
await context.SaveChangesAsync();
}
- (await _service.GetAvatar(username)).Should().BeEquivalentTo(ToAvatarInfo(MockAvatarEntity1));
+ (await _service.GetAvatar(username)).Should().BeEquivalentTo(ToAvatarInfo(mockAvatarEntity));
}
- [Fact]
- public void SetAvatar_ShouldThrow_ArgumentException()
+ public static IEnumerable<object[]> SetAvatar_ShouldThrow_Data()
{
- var avatar = ToAvatar(MockAvatarEntity1);
- // no need to await because arguments are checked syncronizedly.
- _service.Invoking(s => s.SetAvatar(null, avatar)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.SetAvatar("", avatar)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
-
- _service.Invoking(s => s.SetAvatar("aaa", new Avatar { Type = null, Data = new[] { (byte)0x00 } })).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "avatar" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.SetAvatar("aaa", new Avatar { Type = "", Data = new[] { (byte)0x00 } })).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "avatar" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
-
- _service.Invoking(s => s.SetAvatar("aaa", new Avatar { Type = "aaa", Data = null })).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "avatar" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
+ 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) };
}
- [Fact]
- public void SetAvatar_ShouldThrow_UserNotExistException()
+ [Theory]
+ [MemberData(nameof(SetAvatar_ShouldThrow_Data))]
+ public async Task SetAvatar_ShouldThrow(string username, Avatar avatar, Type exceptionType)
{
- const string username = "usernotexist";
- _service.Awaiting(s => s.SetAvatar(username, ToAvatar(MockAvatarEntity1))).Should().Throw<UserNotExistException>()
- .Where(e => e.Username == username);
+ await _service.Awaiting(s => s.SetAvatar(username, avatar)).Should().ThrowAsync(exceptionType);
}
[Fact]
public async Task SetAvatar_Should_Work()
{
- const string username = MockUsers.UserUsername;
+ string username = MockUser.User.Username;
var user = await _database.DatabaseContext.Users.Where(u => u.Name == 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
- var avatar1 = ToAvatar(MockAvatarEntity1);
+ _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().NotBeNull();
+ user.Avatar.ETag.Should().Be(etag1);
+ user.Avatar.LastModified.Should().Be(dateTime1);
// modify
- var avatar2 = ToAvatar(MockAvatarEntity2);
+ _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(MockAvatarEntity2.Type);
- user.Avatar.Data.Should().Equal(MockAvatarEntity2.Data);
- user.Avatar.ETag.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/UserDetailServiceTest.cs b/Timeline.Tests/UserDetailServiceTest.cs deleted file mode 100644 index 98613429..00000000 --- a/Timeline.Tests/UserDetailServiceTest.cs +++ /dev/null @@ -1,275 +0,0 @@ -using FluentAssertions;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-using Timeline.Models;
-using Timeline.Services;
-using Timeline.Tests.Helpers;
-using Timeline.Tests.Mock.Data;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests
-{
- public class UserDetailServiceTest : IDisposable
- {
- private readonly ILoggerFactory _loggerFactory;
- private readonly TestDatabase _database;
-
- private readonly UserDetailService _service;
-
- public UserDetailServiceTest(ITestOutputHelper outputHelper)
- {
- _loggerFactory = Logging.Create(outputHelper);
- _database = new TestDatabase();
-
- _service = new UserDetailService(_loggerFactory.CreateLogger<UserDetailService>(), _database.DatabaseContext);
- }
-
- public void Dispose()
- {
- _loggerFactory.Dispose();
- _database.Dispose();
- }
-
- [Fact]
- public void GetNickname_ShouldThrow_ArgumentException()
- {
- // no need to await because arguments are checked syncronizedly.
- _service.Invoking(s => s.GetUserNickname(null)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.GetUserNickname("")).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
- }
-
- [Fact]
- public void GetNickname_ShouldThrow_UserNotExistException()
- {
- const string username = "usernotexist";
- _service.Awaiting(s => s.GetUserNickname(username)).Should().Throw<UserNotExistException>()
- .Where(e => e.Username == username);
- }
-
- [Fact]
- public async Task GetNickname_Should_Create_And_ReturnDefault()
- {
- {
- var nickname = await _service.GetUserNickname(MockUsers.UserUsername);
- nickname.Should().BeNull();
- }
-
- {
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var detail = context.UserDetails.Where(e => e.UserId == userId).Single();
- detail.Nickname.Should().BeNullOrEmpty();
- detail.QQ.Should().BeNullOrEmpty();
- detail.Email.Should().BeNullOrEmpty();
- detail.PhoneNumber.Should().BeNullOrEmpty();
- detail.Description.Should().BeNullOrEmpty();
- }
- }
-
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData("nickname")]
- public async Task GetNickname_Should_ReturnData(string nickname)
- {
- {
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var entity = new UserDetailEntity
- {
- Nickname = nickname,
- UserId = userId
- };
- context.Add(entity);
- await context.SaveChangesAsync();
- }
-
- {
- var n = await _service.GetUserNickname(MockUsers.UserUsername);
- n.Should().Equals(string.IsNullOrEmpty(nickname) ? null : nickname);
- }
- }
-
- [Fact]
- public void GetDetail_ShouldThrow_ArgumentException()
- {
- // no need to await because arguments are checked syncronizedly.
- _service.Invoking(s => s.GetUserDetail(null)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.GetUserDetail("")).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
- }
-
- [Fact]
- public void GetDetail_ShouldThrow_UserNotExistException()
- {
- const string username = "usernotexist";
- _service.Awaiting(s => s.GetUserDetail(username)).Should().Throw<UserNotExistException>()
- .Where(e => e.Username == username);
- }
-
- [Fact]
- public async Task GetDetail_Should_Create_And_ReturnDefault()
- {
- {
- var detail = await _service.GetUserDetail(MockUsers.UserUsername);
- detail.Should().BeEquivalentTo(new UserDetail());
- }
-
- {
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var detail = context.UserDetails.Where(e => e.UserId == userId).Single();
- detail.Nickname.Should().BeNullOrEmpty();
- detail.QQ.Should().BeNullOrEmpty();
- detail.Email.Should().BeNullOrEmpty();
- detail.PhoneNumber.Should().BeNullOrEmpty();
- detail.Description.Should().BeNullOrEmpty();
- }
- }
-
- [Fact]
- public async Task GetDetail_Should_ReturnData()
- {
- const string email = "ha@aaa.net";
- const string description = "hahaha";
-
-
- {
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var entity = new UserDetailEntity
- {
- Email = email,
- Description = description,
- UserId = userId
- };
- context.Add(entity);
- await context.SaveChangesAsync();
- }
-
- {
- var detail = await _service.GetUserDetail(MockUsers.UserUsername);
- detail.Should().BeEquivalentTo(new UserDetail
- {
- Email = email,
- Description = description
- });
- }
- }
-
- [Fact]
- public void UpdateDetail_ShouldThrow_ArgumentException()
- {
- // no need to await because arguments are checked syncronizedly.
- _service.Invoking(s => s.UpdateUserDetail(null, new UserDetail())).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.UpdateUserDetail("", new UserDetail())).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.UpdateUserDetail("aaa", null)).Should().Throw<ArgumentException>()
- .Where(e => e.ParamName == "detail");
- }
-
- [Fact]
- public void UpdateDetail_ShouldThrow_UserNotExistException()
- {
- const string username = "usernotexist";
- _service.Awaiting(s => s.UpdateUserDetail(username, new UserDetail())).Should().Throw<UserNotExistException>()
- .Where(e => e.Username == username);
- }
-
- [Fact]
- public async Task UpdateDetail_Empty_Should_Work()
- {
- await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail());
-
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var entity = context.UserDetails.Where(e => e.UserId == userId).Single();
- entity.Nickname.Should().BeNullOrEmpty();
- entity.QQ.Should().BeNullOrEmpty();
- entity.Email.Should().BeNullOrEmpty();
- entity.PhoneNumber.Should().BeNullOrEmpty();
- entity.Description.Should().BeNullOrEmpty();
- }
-
- [Theory]
- [InlineData(nameof(UserDetail.Nickname), nameof(UserDetailEntity.Nickname), "aaaa", "bbbb")]
- [InlineData(nameof(UserDetail.QQ), nameof(UserDetailEntity.QQ), "12345678910", "987654321")]
- [InlineData(nameof(UserDetail.Email), nameof(UserDetailEntity.Email), "aaa@aaa.aaa", "bbb@bbb.bbb")]
- [InlineData(nameof(UserDetail.PhoneNumber), nameof(UserDetailEntity.PhoneNumber), "12345678910", "987654321")]
- [InlineData(nameof(UserDetail.Description), nameof(UserDetailEntity.Description), "descriptionA", "descriptionB")]
- public async Task UpdateDetail_Single_Should_Work(string propertyName, string entityPropertyName, string mockData1, string mockData2)
- {
-
- UserDetail CreateWith(string propertyValue)
- {
- var detail = new UserDetail();
- typeof(UserDetail).GetProperty(propertyName).SetValue(detail, propertyValue);
- return detail;
- }
-
- await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(mockData1));
-
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var entity = context.UserDetails.Where(e => e.UserId == userId).Single();
-
- void TestWith(string propertyValue)
- {
- typeof(UserDetailEntity).GetProperty(entityPropertyName).GetValue(entity).Should().Equals(propertyValue);
- foreach (var p in typeof(UserDetailEntity).GetProperties().Where(p => p.Name != entityPropertyName))
- (p.GetValue(entity) as string).Should().BeNullOrEmpty();
- }
-
- TestWith(mockData1);
-
- await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(mockData2));
- TestWith(mockData2);
- await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(""));
- TestWith("");
- }
-
- [Fact]
- public async Task UpdateDetail_Multiple_Should_Work()
- {
- var detail = new UserDetail
- {
- QQ = "12345678",
- Email = "aaa@aaa.aaa",
- PhoneNumber = "11111111111",
- Description = "aaaaaaaaaa"
- };
-
- await _service.UpdateUserDetail(MockUsers.UserUsername, detail);
-
- var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
- var entity = context.UserDetails.Where(e => e.UserId == userId).Single();
- entity.QQ.Should().Equals(detail.QQ);
- entity.Email.Should().Equals(detail.Email);
- entity.PhoneNumber.Should().Equals(detail.PhoneNumber);
- entity.Description.Should().Equals(detail.Description);
-
- var detail2 = new UserDetail
- {
- QQ = null,
- Email = "bbb@bbb.bbb",
- PhoneNumber = "",
- Description = "bbbbbbbbb"
- };
-
- await _service.UpdateUserDetail(MockUsers.UserUsername, detail2);
- entity.QQ.Should().Equals(detail.QQ);
- entity.Email.Should().Equals(detail2.Email);
- entity.PhoneNumber.Should().BeNullOrEmpty();
- entity.Description.Should().Equals(detail2.Description);
- }
- }
-}
diff --git a/Timeline.Tests/UserDetailValidatorTest.cs b/Timeline.Tests/UserDetailValidatorTest.cs deleted file mode 100644 index 9b112946..00000000 --- a/Timeline.Tests/UserDetailValidatorTest.cs +++ /dev/null @@ -1,97 +0,0 @@ -using FluentAssertions;
-using System.Collections.Generic;
-using Timeline.Models.Validation;
-using Xunit;
-
-namespace Timeline.Tests
-{
- public static class UserDetailValidatorsTest
- {
- private static void SucceedWith<TValidator>(object value) where TValidator : class, IValidator, new()
- {
- var result = new TValidator().Validate(value, out var message);
- result.Should().BeTrue();
- message.Should().Equals(ValidationConstants.SuccessMessage);
- }
-
- private static void FailWith<TValidator>(object value, params string[] messageContains) where TValidator : class, IValidator, new()
- {
- var result = new TValidator().Validate(value, out var message);
- result.Should().BeFalse();
-
- foreach (var m in messageContains)
- {
- message.Should().ContainEquivalentOf(m);
- }
- }
-
- public class QQ
- {
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData("12345678")]
- public void Success(object qq)
- {
- SucceedWith<UserDetailValidators.QQValidator>(qq);
- }
-
- [Theory]
- [InlineData(123, "type")]
- [InlineData("123", "short")]
- [InlineData("111111111111111111111111111111111111", "long")]
- [InlineData("aaaaaaaa", "digit")]
- public void Fail(object qq, string messageContains)
- {
- FailWith<UserDetailValidators.QQValidator>(qq, messageContains);
- }
- }
-
- public class EMail
- {
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData("aaa@aaa.net")]
- public void Success(object email)
- {
- SucceedWith<UserDetailValidators.EMailValidator>(email);
- }
-
- public static IEnumerable<object[]> FailTestData()
- {
- yield return new object[] { 123, "type" };
- yield return new object[] { new string('a', 100), "long" };
- yield return new object[] { "aaaaaaaa", "format" };
- }
-
- [Theory]
- [MemberData(nameof(FailTestData))]
- public void Fail(object email, string messageContains)
- {
- FailWith<UserDetailValidators.EMailValidator>(email, messageContains);
- }
- }
-
- public class PhoneNumber
- {
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData("12345678910")]
- public void Success(object phoneNumber)
- {
- SucceedWith<UserDetailValidators.PhoneNumberValidator>(phoneNumber);
- }
-
- [Theory]
- [InlineData(123, "type")]
- [InlineData("111111111111111111111111111111111111", "long")]
- [InlineData("aaaaaaaa", "digit")]
- public void Fail(object phoneNumber, string messageContains)
- {
- FailWith<UserDetailValidators.PhoneNumberValidator>(phoneNumber, messageContains);
- }
- }
- }
-}
diff --git a/Timeline.Tests/UserUnitTest.cs b/Timeline.Tests/UserUnitTest.cs deleted file mode 100644 index 77ec37ee..00000000 --- a/Timeline.Tests/UserUnitTest.cs +++ /dev/null @@ -1,318 +0,0 @@ -using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using System;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Timeline.Controllers;
-using Timeline.Models;
-using Timeline.Models.Http;
-using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Timeline.Tests.Mock.Data;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests
-{
- public class UserUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
- {
- private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
-
- public UserUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
- {
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
- }
-
- public void Dispose()
- {
- _disposeAction();
- }
-
- [Fact]
- public async Task Get_Users_List()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.GetAsync("users");
- res.Should().HaveStatusCodeOk().And.Should().HaveBodyAsJson<UserInfo[]>()
- .Which.Should().BeEquivalentTo(MockUsers.UserInfos);
- }
- }
-
- [Fact]
- public async Task Get_Users_User()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.GetAsync("users/" + MockUsers.UserUsername);
- res.Should().HaveStatusCodeOk()
- .And.Should().HaveBodyAsJson<UserInfo>()
- .Which.Should().BeEquivalentTo(MockUsers.UserUserInfo);
- }
- }
-
- [Fact]
- public async Task Get_Users_404()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.GetAsync("users/usernotexist");
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Get_NotExist);
- }
- }
-
- [Fact]
- public async Task Put_InvalidModel()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- const string url = "users/aaaaaaaa";
- // missing password
- await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = null, Administrator = false });
- // missing administrator
- await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = "???", Administrator = null });
- }
- }
-
- [Fact]
- public async Task Put_BadUsername()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.PutAsJsonAsync("users/dsf fddf", new UserPutRequest
- {
- Password = "???",
- Administrator = false
- });
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Put_BadUsername);
- }
- }
-
- private async Task CheckAdministrator(HttpClient client, string username, bool administrator)
- {
- var res = await client.GetAsync("users/" + username);
- res.Should().HaveStatusCodeOk()
- .And.Should().HaveBodyAsJson<UserInfo>()
- .Which.Administrator.Should().Be(administrator);
- }
-
- [Fact]
- public async Task Put_Modiefied()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.PutAsJsonAsync("users/" + MockUsers.UserUsername, new UserPutRequest
- {
- Password = "password",
- Administrator = false
- });
- res.Should().BePutModified();
- await CheckAdministrator(client, MockUsers.UserUsername, false);
- }
- }
-
- [Fact]
- public async Task Put_Created()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- const string username = "puttest";
- const string url = "users/" + username;
-
- var res = await client.PutAsJsonAsync(url, new UserPutRequest
- {
- Password = "password",
- Administrator = false
- });
- res.Should().BePutCreated();
- await CheckAdministrator(client, username, false);
- }
- }
-
- [Fact]
- public async Task Patch_NotExist()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Patch_NotExist);
- }
- }
-
- [Fact]
- public async Task Patch_Success()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- {
- var res = await client.PatchAsJsonAsync("users/" + MockUsers.UserUsername,
- new UserPatchRequest { Administrator = false });
- res.Should().HaveStatusCodeOk();
- await CheckAdministrator(client, MockUsers.UserUsername, false);
- }
- }
- }
-
- [Fact]
- public async Task Delete_Deleted()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- {
- var url = "users/" + MockUsers.UserUsername;
- var res = await client.DeleteAsync(url);
- res.Should().BeDeleteDeleted();
-
- var res2 = await client.GetAsync(url);
- res2.Should().HaveStatusCodeNotFound();
- }
- }
- }
-
- [Fact]
- public async Task Delete_NotExist()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- {
- var res = await client.DeleteAsync("users/usernotexist");
- res.Should().BeDeleteNotExist();
- }
- }
- }
-
-
- public class ChangeUsernameUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
- {
- private const string url = "userop/changeusername";
-
- private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
-
- public ChangeUsernameUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
- {
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
- }
-
- public void Dispose()
- {
- _disposeAction();
- }
-
- [Fact]
- public async Task InvalidModel()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- // missing old username
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangeUsernameRequest { OldUsername= null, NewUsername= "hhh" });
- // missing new username
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangeUsernameRequest { OldUsername= "hhh", NewUsername= null });
- // bad username
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangeUsernameRequest { OldUsername = "hhh", NewUsername = "???" });
- }
- }
-
- [Fact]
- public async Task UserNotExist()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.PostAsJsonAsync(url,
- new ChangeUsernameRequest{ OldUsername= "usernotexist", NewUsername= "newUsername" });
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.ChangeUsername_NotExist);
- }
- }
-
- [Fact]
- public async Task UserAlreadyExist()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.PostAsJsonAsync(url,
- new ChangeUsernameRequest { OldUsername = MockUsers.UserUsername, NewUsername = MockUsers.AdminUsername });
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.ChangeUsername_AlreadyExist);
- }
- }
-
- [Fact]
- public async Task Success()
- {
- using (var client = await _factory.CreateClientAsAdmin())
- {
- const string newUsername = "hahaha";
- var res = await client.PostAsJsonAsync(url,
- new ChangeUsernameRequest { OldUsername = MockUsers.UserUsername, NewUsername = newUsername });
- res.Should().HaveStatusCodeOk();
- await client.CreateUserTokenAsync(newUsername, MockUsers.UserPassword);
- }
- }
- }
-
-
- public class ChangePasswordUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
- {
- private const string url = "userop/changepassword";
-
- private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
-
- public ChangePasswordUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
- {
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
- }
-
- public void Dispose()
- {
- _disposeAction();
- }
-
- [Fact]
- public async Task InvalidModel()
- {
- using (var client = await _factory.CreateClientAsUser())
- {
- // missing old password
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangePasswordRequest { OldPassword = null, NewPassword = "???" });
- // missing new password
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangePasswordRequest { OldPassword = "???", NewPassword = null });
- }
- }
-
- [Fact]
- public async Task BadOldPassword()
- {
- using (var client = await _factory.CreateClientAsUser())
- {
- var res = await client.PostAsJsonAsync(url, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" });
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.ChangePassword_BadOldPassword);
- }
- }
-
- [Fact]
- public async Task Success()
- {
- using (var client = await _factory.CreateClientAsUser())
- {
- const string newPassword = "new";
- var res = await client.PostAsJsonAsync(url,
- new ChangePasswordRequest { OldPassword = MockUsers.UserPassword, NewPassword = newPassword });
- res.Should().HaveStatusCodeOk();
- await client.CreateUserTokenAsync(MockUsers.UserUsername, newPassword);
- }
- }
- }
- }
-}
diff --git a/Timeline.Tests/UsernameValidatorUnitTest.cs b/Timeline.Tests/UsernameValidatorUnitTest.cs index 6a635ba1..283e18e2 100644 --- a/Timeline.Tests/UsernameValidatorUnitTest.cs +++ b/Timeline.Tests/UsernameValidatorUnitTest.cs @@ -15,15 +15,9 @@ namespace Timeline.Tests private string FailAndMessage(string username)
{
- var result = _validator.Validate(username, out var message);
+ var (result, messageGenerator) = _validator.Validate(username);
result.Should().BeFalse();
- return message;
- }
-
- private void Succeed(string username)
- {
- _validator.Validate(username, out var message).Should().BeTrue();
- message.Should().Be(ValidationConstants.SuccessMessage);
+ return messageGenerator(null);
}
[Fact]
@@ -35,8 +29,9 @@ namespace Timeline.Tests [Fact]
public void NotString()
{
- var result = _validator.Validate(123, out var message);
+ var (result, messageGenerator) = _validator.Validate(123);
result.Should().BeFalse();
+ var message = messageGenerator(null);
message.Should().ContainEquivalentOf("type");
}
@@ -46,31 +41,14 @@ namespace Timeline.Tests FailAndMessage("").Should().ContainEquivalentOf("empty");
}
- [Fact]
- public void WhiteSpace()
+ [Theory]
+ [InlineData("!")]
+ [InlineData("!abc")]
+ [InlineData("ab c")]
+ public void BadCharactor(string value)
{
- FailAndMessage(" ").Should().ContainEquivalentOf("whitespace");
- FailAndMessage("\t").Should().ContainEquivalentOf("whitespace");
- FailAndMessage("\n").Should().ContainEquivalentOf("whitespace");
-
- FailAndMessage("a b").Should().ContainEquivalentOf("whitespace");
- FailAndMessage("a\tb").Should().ContainEquivalentOf("whitespace");
- FailAndMessage("a\nb").Should().ContainEquivalentOf("whitespace");
- }
-
- [Fact]
- public void BadCharactor()
- {
- FailAndMessage("!").Should().ContainEquivalentOf("regex");
- FailAndMessage("!abc").Should().ContainEquivalentOf("regex");
- FailAndMessage("ab!c").Should().ContainEquivalentOf("regex");
- }
-
- [Fact]
- public void BadBegin()
- {
- FailAndMessage("-").Should().ContainEquivalentOf("regex");
- FailAndMessage("-abc").Should().ContainEquivalentOf("regex");
+ FailAndMessage(value).Should().ContainEquivalentOf("invalid")
+ .And.ContainEquivalentOf("character");
}
[Fact]
@@ -79,14 +57,20 @@ namespace Timeline.Tests FailAndMessage(new string('a', 40)).Should().ContainEquivalentOf("long");
}
- [Fact]
- public void Success()
+ [Theory]
+ [InlineData("abc")]
+ [InlineData("-abc")]
+ [InlineData("_abc")]
+ [InlineData("abc-")]
+ [InlineData("abc_")]
+ [InlineData("a-bc")]
+ [InlineData("a-b-c")]
+ [InlineData("a-b_c")]
+ public void Success(string value)
{
- Succeed("abc");
- Succeed("_abc");
- Succeed("a-bc");
- Succeed("a-b-c");
- Succeed("a-b_c");
+
+ var (result, _) = _validator.Validate(value);
+ result.Should().BeTrue();
}
}
}
|