diff options
author | 杨宇千 <crupest@outlook.com> | 2019-10-21 13:41:46 +0800 |
---|---|---|
committer | 杨宇千 <crupest@outlook.com> | 2019-10-21 13:41:46 +0800 |
commit | 5e64e3385ae8eb9b877c032418da9e5086d50a06 (patch) | |
tree | 705580fda162d3a2667f288ddb8092e8fba09703 | |
parent | 38cef20cd509648d50e289cd4c7ec4a772031b12 (diff) | |
download | timeline-5e64e3385ae8eb9b877c032418da9e5086d50a06.tar.gz timeline-5e64e3385ae8eb9b877c032418da9e5086d50a06.tar.bz2 timeline-5e64e3385ae8eb9b877c032418da9e5086d50a06.zip |
...
-rw-r--r-- | Timeline.Tests/Controllers/TokenControllerTest.cs | 13 | ||||
-rw-r--r-- | Timeline.Tests/Controllers/UserControllerTest.cs | 109 | ||||
-rw-r--r-- | Timeline.Tests/GlobalSuppressions.cs | 5 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/AssertionResponseExtensions.cs | 46 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/HttpClientExtensions.cs | 13 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/ImageHelper.cs | 24 | ||||
-rw-r--r-- | Timeline.Tests/IntegratedTests/UserUnitTest.cs | 22 | ||||
-rw-r--r-- | Timeline.Tests/Mock/Services/MockStringLocalizer.cs | 31 | ||||
-rw-r--r-- | Timeline.Tests/Mock/Services/TestStringLocalizerFactory.cs | 25 | ||||
-rw-r--r-- | Timeline/Controllers/TokenController.cs | 4 | ||||
-rw-r--r-- | Timeline/Controllers/UserController.cs | 39 | ||||
-rw-r--r-- | Timeline/Models/Http/Common.cs | 42 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/UserController.en.resx | 9 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/UserController.resx | 15 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/UserController.zh.resx | 9 |
15 files changed, 281 insertions, 125 deletions
diff --git a/Timeline.Tests/Controllers/TokenControllerTest.cs b/Timeline.Tests/Controllers/TokenControllerTest.cs index 86a241e5..71520e77 100644 --- a/Timeline.Tests/Controllers/TokenControllerTest.cs +++ b/Timeline.Tests/Controllers/TokenControllerTest.cs @@ -20,13 +20,14 @@ namespace Timeline.Tests.Controllers 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,
- new MockStringLocalizer<TokenController>());
+ TestStringLocalizerFactory.Create().Create<TokenController>());
}
public void Dispose()
@@ -53,7 +54,7 @@ namespace Timeline.Tests.Controllers Password = "p",
Expire = expire
});
- action.Should().BeAssignableTo<OkObjectResult>()
+ action.Result.Should().BeAssignableTo<OkObjectResult>()
.Which.Value.Should().BeEquivalentTo(createResult);
}
@@ -67,7 +68,7 @@ namespace Timeline.Tests.Controllers Password = "p",
Expire = null
});
- action.Should().BeAssignableTo<BadRequestObjectResult>()
+ action.Result.Should().BeAssignableTo<BadRequestObjectResult>()
.Which.Value.Should().BeAssignableTo<CommonResponse>()
.Which.Code.Should().Be(Create.BadCredential);
}
@@ -82,7 +83,7 @@ namespace Timeline.Tests.Controllers Password = "p",
Expire = null
});
- action.Should().BeAssignableTo<BadRequestObjectResult>()
+ action.Result.Should().BeAssignableTo<BadRequestObjectResult>()
.Which.Value.Should().BeAssignableTo<CommonResponse>()
.Which.Code.Should().Be(Create.BadCredential);
}
@@ -93,7 +94,7 @@ namespace Timeline.Tests.Controllers const string token = "aaaaaaaaaaaaaa";
_mockUserService.Setup(s => s.VerifyToken(token)).ReturnsAsync(MockUser.User.Info);
var action = await _controller.Verify(new VerifyTokenRequest { Token = token });
- action.Should().BeAssignableTo<OkObjectResult>()
+ action.Result.Should().BeAssignableTo<OkObjectResult>()
.Which.Value.Should().BeAssignableTo<VerifyTokenResponse>()
.Which.User.Should().BeEquivalentTo(MockUser.User.Info);
}
@@ -113,7 +114,7 @@ namespace Timeline.Tests.Controllers const string token = "aaaaaaaaaaaaaa";
_mockUserService.Setup(s => s.VerifyToken(token)).ThrowsAsync(e);
var action = await _controller.Verify(new VerifyTokenRequest { Token = token });
- action.Should().BeAssignableTo<BadRequestObjectResult>()
+ 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..9fec477f --- /dev/null +++ b/Timeline.Tests/Controllers/UserControllerTest.cs @@ -0,0 +1,109 @@ +using FluentAssertions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using System;
+using System.Linq;
+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.Created, true)]
+ [InlineData(PutResult.Modified, 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 Put_BadUsername()
+ {
+ const string username = "aaa";
+ const string password = "ppp";
+ const bool administrator = true;
+ _mockUserService.Setup(s => s.PutUser(username, password, administrator)).ThrowsAsync(new UsernameBadFormatException());
+ var action = await _controller.Put(new UserPutRequest
+ {
+ Password = password,
+ Administrator = administrator
+ }, username);
+ action.Result.Should().BeAssignableTo<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(Put.BadUsername);
+ }
+
+ //TODO! Complete this.
+ }
+}
diff --git a/Timeline.Tests/GlobalSuppressions.cs b/Timeline.Tests/GlobalSuppressions.cs index 6562efbb..2191a5c4 100644 --- a/Timeline.Tests/GlobalSuppressions.cs +++ b/Timeline.Tests/GlobalSuppressions.cs @@ -5,5 +5,10 @@ [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", "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 5ce025ee..08f10b2b 100644 --- a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs +++ b/Timeline.Tests/Helpers/AssertionResponseExtensions.cs @@ -82,22 +82,14 @@ namespace Timeline.Tests.Helpers {
body = Subject.Content.ReadAsStringAsync().Result;
}
- catch (Exception e)
+ catch (AggregateException 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);
+ 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("Expected response body of {context:HttpResponseMessage} to be able to convert to {0} instance{reason}, but failed. Exception is {1}.", typeof(T).FullName, e);
- return new AndWhichConstraint<HttpResponseMessage, T>(Subject, null);
- }
+ var result = JsonConvert.DeserializeObject<T>(body);
+ return new AndWhichConstraint<HttpResponseMessage, T>(Subject, result);
}
}
@@ -118,28 +110,22 @@ namespace Timeline.Tests.Helpers return assertions.HaveJsonBody<CommonDataResponse<TData>>(because, becauseArgs);
}
- public static void BePutCreate(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- assertions.HaveStatusCode(201, because, becauseArgs)
- .And.Should().HaveCommonDataBody<CommonPutResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Create(), because, becauseArgs);
- }
-
- public static void BePutModify(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
- {
- assertions.HaveStatusCode(200, because, becauseArgs)
- .And.Should().HaveCommonDataBody<CommonPutResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Modify(), because, becauseArgs);
- }
-
- public static void BeDeleteDelete(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BePut(this HttpResponseMessageAssertions assertions, bool create, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCode(200, because, becauseArgs)
- .And.Should().HaveCommonDataBody<CommonDeleteResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.Delete(), 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 BeDeleteNotExist(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BeDelete(this HttpResponseMessageAssertions assertions, bool delete, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCode(200, because, becauseArgs)
- .And.Should().HaveCommonDataBody<CommonDeleteResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.NotExist(), 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 BeInvalidModel(this HttpResponseMessageAssertions assertions, string message = null)
diff --git a/Timeline.Tests/Helpers/HttpClientExtensions.cs b/Timeline.Tests/Helpers/HttpClientExtensions.cs index e3beea1d..38641f90 100644 --- a/Timeline.Tests/Helpers/HttpClientExtensions.cs +++ b/Timeline.Tests/Helpers/HttpClientExtensions.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json;
+using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
@@ -11,12 +12,24 @@ namespace Timeline.Tests.Helpers {
public static Task<HttpResponseMessage> PatchAsJsonAsync<T>(this HttpClient client, string url, T body)
{
+ 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/IntegratedTests/UserUnitTest.cs b/Timeline.Tests/IntegratedTests/UserUnitTest.cs index b2aab24c..47a8699c 100644 --- a/Timeline.Tests/IntegratedTests/UserUnitTest.cs +++ b/Timeline.Tests/IntegratedTests/UserUnitTest.cs @@ -4,13 +4,13 @@ using System; using System.Collections.Generic;
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 static Timeline.ErrorCodes.Http.User;
namespace Timeline.Tests.IntegratedTests
{
@@ -57,7 +57,7 @@ namespace Timeline.Tests.IntegratedTests var res = await client.GetAsync("users/usernotexist");
res.Should().HaveStatusCode(404)
.And.Should().HaveCommonBody()
- .Which.Code.Should().Be(UserController.ErrorCodes.Get_NotExist);
+ .Which.Code.Should().Be(Get.NotExist);
}
public static IEnumerable<object[]> Put_InvalidModel_Data()
@@ -88,7 +88,7 @@ namespace Timeline.Tests.IntegratedTests });
res.Should().HaveStatusCode(400)
.And.Should().HaveCommonBody()
- .Which.Code.Should().Be(UserController.ErrorCodes.Put_BadUsername);
+ .Which.Code.Should().Be(Put.BadUsername);
}
private async Task CheckAdministrator(HttpClient client, string username, bool administrator)
@@ -108,7 +108,7 @@ namespace Timeline.Tests.IntegratedTests Password = "password",
Administrator = false
});
- res.Should().BePutModify();
+ res.Should().BePut(false);
await CheckAdministrator(client, MockUser.User.Username, false);
}
@@ -124,7 +124,7 @@ namespace Timeline.Tests.IntegratedTests Password = "password",
Administrator = false
});
- res.Should().BePutCreate();
+ res.Should().BePut(true);
await CheckAdministrator(client, username, false);
}
@@ -135,7 +135,7 @@ namespace Timeline.Tests.IntegratedTests var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
res.Should().HaveStatusCode(404)
.And.Should().HaveCommonBody()
- .Which.Code.Should().Be(UserController.ErrorCodes.Patch_NotExist);
+ .Which.Code.Should().Be(Patch.NotExist);
}
[Fact]
@@ -156,7 +156,7 @@ namespace Timeline.Tests.IntegratedTests using var client = await _factory.CreateClientAsAdmin();
var url = "users/" + MockUser.User.Username;
var res = await client.DeleteAsync(url);
- res.Should().BeDeleteDelete();
+ res.Should().BeDelete(true);
var res2 = await client.GetAsync(url);
res2.Should().HaveStatusCode(404);
@@ -167,7 +167,7 @@ namespace Timeline.Tests.IntegratedTests {
using var client = await _factory.CreateClientAsAdmin();
var res = await client.DeleteAsync("users/usernotexist");
- res.Should().BeDeleteNotExist();
+ res.Should().BeDelete(false);
}
@@ -214,7 +214,7 @@ namespace Timeline.Tests.IntegratedTests new ChangeUsernameRequest { OldUsername = "usernotexist", NewUsername = "newUsername" });
res.Should().HaveStatusCode(400)
.And.Should().HaveCommonBody()
- .Which.Code.Should().Be(UserController.ErrorCodes.ChangeUsername_NotExist);
+ .Which.Code.Should().Be(Op.ChangeUsername.NotExist);
}
[Fact]
@@ -225,7 +225,7 @@ namespace Timeline.Tests.IntegratedTests new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = MockUser.Admin.Username });
res.Should().HaveStatusCode(400)
.And.Should().HaveCommonBody()
- .Which.Code.Should().Be(UserController.ErrorCodes.ChangeUsername_AlreadyExist);
+ .Which.Code.Should().Be(Op.ChangeUsername.AlreadyExist);
}
[Fact]
@@ -282,7 +282,7 @@ namespace Timeline.Tests.IntegratedTests var res = await client.PostAsJsonAsync(url, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" });
res.Should().HaveStatusCode(400)
.And.Should().HaveCommonBody()
- .Which.Code.Should().Be(UserController.ErrorCodes.ChangePassword_BadOldPassword);
+ .Which.Code.Should().Be(Op.ChangePassword.BadOldPassword);
}
[Fact]
diff --git a/Timeline.Tests/Mock/Services/MockStringLocalizer.cs b/Timeline.Tests/Mock/Services/MockStringLocalizer.cs deleted file mode 100644 index 7729d56c..00000000 --- a/Timeline.Tests/Mock/Services/MockStringLocalizer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.Extensions.Localization;
-using System.Collections.Generic;
-using System.Globalization;
-
-namespace Timeline.Tests.Mock.Services
-{
- public class MockStringLocalizer : IStringLocalizer
- {
- private const string mockKey = "MOCK_KEY";
- private const string mockString = "THIS IS A MOCK LOCALIZED STRING.";
-
- public LocalizedString this[string name] => new LocalizedString(name, mockString);
-
- public LocalizedString this[string name, params object[] arguments] => new LocalizedString(name, mockString);
-
- public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
- {
- yield return new LocalizedString(mockKey, mockString);
- }
-
- public IStringLocalizer WithCulture(CultureInfo culture)
- {
- return this;
- }
- }
-
- public class MockStringLocalizer<T> : MockStringLocalizer, IStringLocalizer<T>
- {
-
- }
-}
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/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index d708127a..cf32a562 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -56,7 +56,7 @@ namespace Timeline.Controllers [HttpPost("create")]
[AllowAnonymous]
- public async Task<IActionResult> Create([FromBody] CreateTokenRequest request)
+ public async Task<ActionResult<CreateTokenResponse>> Create([FromBody] CreateTokenRequest request)
{
void LogFailure(string reason, Exception? e = null)
{
@@ -102,7 +102,7 @@ namespace Timeline.Controllers [HttpPost("verify")]
[AllowAnonymous]
- public async Task<IActionResult> Verify([FromBody] VerifyTokenRequest request)
+ public async Task<ActionResult<VerifyTokenResponse>> Verify([FromBody] VerifyTokenRequest request)
{
void LogFailure(string reason, Exception? e = null, params (string, object?)[] otherProperties)
{
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index b01d06fb..6afc890c 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -77,7 +77,7 @@ namespace Timeline.Controllers }
[HttpGet("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Get([FromRoute] string username)
+ public async Task<ActionResult<UserInfo>> Get([FromRoute] string username)
{
var user = await _userService.GetUser(username);
if (user == null)
@@ -89,7 +89,7 @@ namespace Timeline.Controllers }
[HttpPut("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Put([FromBody] UserPutRequest request, [FromRoute] string username)
+ public async Task<ActionResult<CommonPutResponse>> Put([FromBody] UserPutRequest request, [FromRoute] string username)
{
try
{
@@ -114,7 +114,7 @@ namespace Timeline.Controllers }
[HttpPatch("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute] string username)
+ public async Task<ActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute] string username)
{
try
{
@@ -129,7 +129,7 @@ namespace Timeline.Controllers }
[HttpDelete("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Delete([FromRoute] string username)
+ public async Task<ActionResult<CommonDeleteResponse>> Delete([FromRoute] string username)
{
try
{
@@ -145,44 +145,45 @@ namespace Timeline.Controllers }
[HttpPost("userop/changeusername"), AdminAuthorize]
- public async Task<IActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
+ public async Task<ActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
{
try
{
await _userService.ChangeUsername(request.OldUsername, request.NewUsername);
- _logger.LogInformation(FormatLogMessage("A user changed username.",
- Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
+ _logger.LogInformation(Log.Format(_localizer["LogChangeUsernameSuccess"],
+ ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
return Ok();
}
catch (UserNotExistException e)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to change a non-existent user's username failed.",
- Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
- return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_NotExist, $"The user {request.OldUsername} does not exist."));
+ _logger.LogInformation(e, Log.Format(_localizer["LogChangeUsernameNotExist"],
+ ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangeUsername.NotExist, _localizer["ErrorChangeUsernameNotExist", request.OldUsername]));
}
catch (UserAlreadyExistException e)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to change a user's username to a existent one failed.",
- Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
- return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_AlreadyExist, $"The user {request.NewUsername} already exists."));
+ _logger.LogInformation(e, Log.Format(_localizer["LogChangeUsernameAlreadyExist"],
+ ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangeUsername.AlreadyExist, _localizer["ErrorChangeUsernameAlreadyExist"]));
}
// there is no need to catch bad format exception because it is already checked in model validation.
}
[HttpPost("userop/changepassword"), Authorize]
- public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
+ public async Task<ActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
{
try
{
- await _userService.ChangePassword(User.Identity.Name, request.OldPassword, request.NewPassword);
- _logger.LogInformation(FormatLogMessage("A user changed password.", Pair("Username", User.Identity.Name)));
+ await _userService.ChangePassword(User.Identity.Name!, request.OldPassword, request.NewPassword);
+ _logger.LogInformation(Log.Format(_localizer["LogChangePasswordSuccess"], ("Username", User.Identity.Name)));
return Ok();
}
catch (BadPasswordException e)
{
- _logger.LogInformation(e, FormatLogMessage("A user attempt to change password but old password is wrong.",
- Pair("Username", User.Identity.Name), Pair("Old Password", request.OldPassword)));
- return BadRequest(new CommonResponse(ErrorCodes.ChangePassword_BadOldPassword, "Old password is wrong."));
+ _logger.LogInformation(e, Log.Format(_localizer["LogChangePasswordBadPassword"],
+ ("Username", User.Identity.Name), ("Old Password", request.OldPassword)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangePassword.BadOldPassword,
+ _localizer["ErrorChangePasswordBadPassword"]));
}
// User can't be non-existent or the token is bad.
}
diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs index 2735e43c..130439d3 100644 --- a/Timeline/Models/Http/Common.cs +++ b/Timeline/Models/Http/Common.cs @@ -61,7 +61,7 @@ namespace Timeline.Models.Http public T Data { get; set; } = default!;
}
- public static class CommonPutResponse
+ public class CommonPutResponse : CommonDataResponse<CommonPutResponse.ResponseData>
{
public class ResponseData
{
@@ -73,21 +73,32 @@ namespace Timeline.Models.Http public bool Create { get; set; }
}
- internal static CommonDataResponse<ResponseData> Create(IStringLocalizerFactory localizerFactory)
+ public CommonPutResponse()
+ {
+
+ }
+
+ public CommonPutResponse(int code, string message, bool create)
+ : base(code, message, new ResponseData(create))
+ {
+
+ }
+
+ internal static CommonPutResponse Create(IStringLocalizerFactory localizerFactory)
{
var localizer = localizerFactory.Create("Http.Common");
- return new CommonDataResponse<ResponseData>(0, localizer["ResponsePutCreate"], new ResponseData(true));
+ return new CommonPutResponse(0, localizer["ResponsePutCreate"], true);
}
- internal static CommonDataResponse<ResponseData> Modify(IStringLocalizerFactory localizerFactory)
+ internal static CommonPutResponse Modify(IStringLocalizerFactory localizerFactory)
{
var localizer = localizerFactory.Create("Http.Common");
- return new CommonDataResponse<ResponseData>(0, localizer["ResponsePutModify"], new ResponseData(false));
+ return new CommonPutResponse(0, localizer["ResponsePutModify"], false);
}
}
- public static class CommonDeleteResponse
+ public class CommonDeleteResponse : CommonDataResponse<CommonDeleteResponse.ResponseData>
{
public class ResponseData
{
@@ -99,16 +110,27 @@ namespace Timeline.Models.Http public bool Delete { get; set; }
}
- internal static CommonDataResponse<ResponseData> Delete(IStringLocalizerFactory localizerFactory)
+ public CommonDeleteResponse()
+ {
+
+ }
+
+ public CommonDeleteResponse(int code, string message, bool delete)
+ : base(code, message, new ResponseData(delete))
+ {
+
+ }
+
+ internal static CommonDeleteResponse Delete(IStringLocalizerFactory localizerFactory)
{
var localizer = localizerFactory.Create("Http.Common");
- return new CommonDataResponse<ResponseData>(0, localizer["ResponseDeleteDelete"], new ResponseData(true));
+ return new CommonDeleteResponse(0, localizer["ResponseDeleteDelete"], true);
}
- internal static CommonDataResponse<ResponseData> NotExist(IStringLocalizerFactory localizerFactory)
+ internal static CommonDeleteResponse NotExist(IStringLocalizerFactory localizerFactory)
{
var localizer = localizerFactory.Create("Http.Common");
- return new CommonDataResponse<ResponseData>(0, localizer["ResponseDeleteNotExist"], new ResponseData(false));
+ return new CommonDeleteResponse(0, localizer["ResponseDeleteNotExist"], false);
}
}
}
diff --git a/Timeline/Resources/Controllers/UserController.en.resx b/Timeline/Resources/Controllers/UserController.en.resx index f0fb372a..0bd1dfe3 100644 --- a/Timeline/Resources/Controllers/UserController.en.resx +++ b/Timeline/Resources/Controllers/UserController.en.resx @@ -117,6 +117,15 @@ <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="ErrorChangePasswordBadPassword" xml:space="preserve">
+ <value>Old password is wrong.</value>
+ </data>
+ <data name="ErrorChangeUsernameAlreadyExist" xml:space="preserve">
+ <value>The new username {0} already exists.</value>
+ </data>
+ <data name="ErrorChangeUsernameNotExist" xml:space="preserve">
+ <value>The old username {0} does not exist.</value>
+ </data>
<data name="ErrorGetUserNotExist" xml:space="preserve">
<value>The user does not exist.</value>
</data>
diff --git a/Timeline/Resources/Controllers/UserController.resx b/Timeline/Resources/Controllers/UserController.resx index 901f8aab..d720d1c1 100644 --- a/Timeline/Resources/Controllers/UserController.resx +++ b/Timeline/Resources/Controllers/UserController.resx @@ -117,6 +117,21 @@ <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="LogChangePasswordBadPassword" xml:space="preserve">
+ <value>Attempt to change password with wrong old password failed.</value>
+ </data>
+ <data name="LogChangePasswordSuccess" xml:space="preserve">
+ <value>A user has changed password.</value>
+ </data>
+ <data name="LogChangeUsernameAlreadyExist" xml:space="preserve">
+ <value>Attempt to change a user's username to a existent one failed.</value>
+ </data>
+ <data name="LogChangeUsernameNotExist" xml:space="preserve">
+ <value>Attempt to change a username of a user that does not exist failed.</value>
+ </data>
+ <data name="LogChangeUsernameSuccess" xml:space="preserve">
+ <value>A user has changed username.</value>
+ </data>
<data name="LogDeleteDelete" xml:space="preserve">
<value>A user has been deleted.</value>
</data>
diff --git a/Timeline/Resources/Controllers/UserController.zh.resx b/Timeline/Resources/Controllers/UserController.zh.resx index 519f08f6..3556083e 100644 --- a/Timeline/Resources/Controllers/UserController.zh.resx +++ b/Timeline/Resources/Controllers/UserController.zh.resx @@ -117,6 +117,15 @@ <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="ErrorChangePasswordBadPassword" xml:space="preserve">
+ <value>旧密码错误。</value>
+ </data>
+ <data name="ErrorChangeUsernameAlreadyExist" xml:space="preserve">
+ <value>新用户名{0}已经存在。</value>
+ </data>
+ <data name="ErrorChangeUsernameNotExist" xml:space="preserve">
+ <value>旧用户名{0}不存在。</value>
+ </data>
<data name="ErrorGetUserNotExist" xml:space="preserve">
<value>用户不存在。</value>
</data>
|