aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-10-17 20:46:57 +0800
committer杨宇千 <crupest@outlook.com>2019-10-17 20:46:57 +0800
commitdef8e8dd78812c019a0d6e8e5a3e2de4e82ae3e4 (patch)
treedc7688d7d2dd5ab28a7e3c553154ee84676f75d2
parent297d0c9029360f1d5334ed843b9b299356740ec1 (diff)
downloadtimeline-def8e8dd78812c019a0d6e8e5a3e2de4e82ae3e4.tar.gz
timeline-def8e8dd78812c019a0d6e8e5a3e2de4e82ae3e4.tar.bz2
timeline-def8e8dd78812c019a0d6e8e5a3e2de4e82ae3e4.zip
...
-rw-r--r--Timeline.Tests/AuthorizationUnitTest.cs76
-rw-r--r--Timeline.Tests/Controllers/TokenControllerTest.cs84
-rw-r--r--Timeline.Tests/Helpers/AssertionResponseExtensions.cs24
-rw-r--r--Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs6
-rw-r--r--Timeline.Tests/Helpers/InvalidModelTestHelpers.cs5
-rw-r--r--Timeline.Tests/Helpers/MyTestLoggerFactory.cs25
-rw-r--r--Timeline.Tests/Helpers/MyWebApplicationFactory.cs73
-rw-r--r--Timeline.Tests/Helpers/TestApplication.cs52
-rw-r--r--Timeline.Tests/IntegratedTests/AuthorizationUnitTest.cs68
-rw-r--r--Timeline.Tests/IntegratedTests/TokenUnitTest.cs (renamed from Timeline.Tests/TokenUnitTest.cs)123
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTests.cs23
-rw-r--r--Timeline.Tests/IntegratedTests/UserDetailTest.cs29
-rw-r--r--Timeline.Tests/IntegratedTests/UserUnitTest.cs (renamed from Timeline.Tests/UserUnitTest.cs)72
-rw-r--r--Timeline.Tests/Mock/Data/TestDatabase.cs25
-rw-r--r--Timeline.Tests/Mock/Data/TestUsers.cs62
-rw-r--r--Timeline.Tests/Mock/Services/TestClock.cs10
-rw-r--r--Timeline.Tests/Timeline.Tests.csproj1
-rw-r--r--Timeline.Tests/UserAvatarServiceTest.cs19
-rw-r--r--Timeline.Tests/UserDetailServiceTest.cs44
-rw-r--r--Timeline/Controllers/TokenController.cs131
-rw-r--r--Timeline/Controllers/UserController.cs8
-rw-r--r--Timeline/ErrorCodes.cs29
-rw-r--r--Timeline/Helpers/Log.cs19
-rw-r--r--Timeline/Models/Http/Common.cs74
-rw-r--r--Timeline/Models/Http/Token.cs2
25 files changed, 584 insertions, 500 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..fff7c020
--- /dev/null
+++ b/Timeline.Tests/Controllers/TokenControllerTest.cs
@@ -0,0 +1,84 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using System;
+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
+ {
+ 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);
+ }
+
+ [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.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.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.Should().BeAssignableTo<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(Create.BadCredential);
+ }
+
+ // TODO! Verify unit tests
+ }
+}
diff --git a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs b/Timeline.Tests/Helpers/AssertionResponseExtensions.cs
index e67a172a..c7ebdb7a 100644
--- a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs
+++ b/Timeline.Tests/Helpers/AssertionResponseExtensions.cs
@@ -110,29 +110,39 @@ namespace Timeline.Tests.Helpers
return assertions.HaveBodyAsJson<CommonResponse>(because, becauseArgs);
}
+ public static AndWhichConstraint<HttpResponseMessage, CommonDataResponse<T>> HaveBodyAsCommonDataResponse<T>(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ {
+ return assertions.HaveBodyAsJson<CommonDataResponse<T>>(because, becauseArgs);
+ }
+
public static void HaveBodyAsCommonResponseWithCode(this HttpResponseMessageAssertions assertions, int expected, string because = "", params object[] becauseArgs)
{
assertions.HaveBodyAsCommonResponse(because, becauseArgs).Which.Code.Should().Be(expected, because, becauseArgs);
}
- public static void BePutCreated(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void HaveBodyAsCommonDataResponseWithCode<T>(this HttpResponseMessageAssertions assertions, int expected, string because = "", params object[] becauseArgs)
+ {
+ assertions.HaveBodyAsCommonDataResponse<T>(because, becauseArgs).Which.Code.Should().Be(expected, because, becauseArgs);
+ }
+
+ public static void BePutCreate(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeCreated(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Created, because, becauseArgs);
+ assertions.HaveStatusCodeCreated(because, becauseArgs).And.Should().HaveBodyAsCommonDataResponse<CommonPutResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Create(), because, becauseArgs);
}
- public static void BePutModified(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BePutModify(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Modified, because, becauseArgs);
+ assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonDataResponse<CommonPutResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonPutResponse.Modify(), because, becauseArgs);
}
- public static void BeDeleteDeleted(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static void BeDeleteDelete(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.Deleted, because, becauseArgs);
+ assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonDataResponse<CommonDeleteResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.Delete(), because, becauseArgs);
}
public static void BeDeleteNotExist(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
- assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonResponse(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.NotExists, because, becauseArgs);
+ assertions.HaveStatusCodeOk(because, becauseArgs).And.Should().HaveBodyAsCommonDataResponse<CommonDeleteResponse.ResponseData>(because, becauseArgs).Which.Should().BeEquivalentTo(CommonDeleteResponse.NotExist(), because, becauseArgs);
}
}
}
diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs
index c8a79e58..d068a08a 100644
--- a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs
+++ b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs
@@ -13,7 +13,7 @@ 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 });
+ var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, Expire = expireOffset });
response.Should().HaveStatusCodeOk();
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/InvalidModelTestHelpers.cs b/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs
index af432095..4a445ca4 100644
--- a/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs
+++ b/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs
@@ -1,6 +1,5 @@
using System.Net.Http;
using System.Threading.Tasks;
-using Timeline.Models.Http;
namespace Timeline.Tests.Helpers
{
@@ -10,14 +9,14 @@ namespace Timeline.Tests.Helpers
{
var response = await client.PostAsJsonAsync(url, body);
response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.InvalidModel);
+ .And.Should().HaveBodyAsCommonResponseWithCode(ErrorCodes.Http.Common.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);
+ .And.Should().HaveBodyAsCommonResponseWithCode(ErrorCodes.Http.Common.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/AuthorizationUnitTest.cs b/Timeline.Tests/IntegratedTests/AuthorizationUnitTest.cs
new file mode 100644
index 00000000..a67bffcf
--- /dev/null
+++ b/Timeline.Tests/IntegratedTests/AuthorizationUnitTest.cs
@@ -0,0 +1,68 @@
+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 AuthorizationUnitTest : IClassFixture<WebApplicationFactory<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 TestApplication _testApp;
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public AuthorizationUnitTest(WebApplicationFactory<Startup> factory)
+ {
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
+ }
+
+ public void Dispose()
+ {
+ _testApp.Dispose();
+ }
+
+ [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/TokenUnitTest.cs b/Timeline.Tests/IntegratedTests/TokenUnitTest.cs
index 3babacf7..05e2b3e5 100644
--- a/Timeline.Tests/TokenUnitTest.cs
+++ b/Timeline.Tests/IntegratedTests/TokenUnitTest.cs
@@ -3,68 +3,63 @@ using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
-using Timeline.Controllers;
+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 Timeline.Tests.Mock.Services;
using Xunit;
-using Xunit.Abstractions;
+using static Timeline.ErrorCodes.Http.Token;
namespace Timeline.Tests
{
- public class TokenUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
+ public class TokenUnitTest : 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;
- private readonly Action _disposeAction;
- public TokenUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ public TokenUnitTest(WebApplicationFactory<Startup> factory)
{
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
}
public void Dispose()
{
- _disposeAction();
+ _testApp.Dispose();
}
-
[Fact]
- public async void CreateToken_InvalidModel()
+ public async Task 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
- });
- }
+ 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 = "user",
+ Password = "password",
+ Expire = 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);
- }
+ using var client = _factory.CreateDefaultClient();
+ var response = await client.PostAsJsonAsync(CreateTokenUrl,
+ new CreateTokenRequest { Username = "usernotexist", Password = "???" });
+ response.Should().HaveStatusCodeBadRequest()
+ .And.Should().HaveBodyAsCommonResponseWithCode(Create.BadCredential);
}
[Fact]
@@ -73,9 +68,9 @@ namespace Timeline.Tests
using (var client = _factory.CreateDefaultClient())
{
var response = await client.PostAsJsonAsync(CreateTokenUrl,
- new CreateTokenRequest { Username = MockUsers.UserUsername, Password = "???" });
+ new CreateTokenRequest { Username = MockUser.User.Username, Password = "???" });
response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Create_BadPassword);
+ .And.Should().HaveBodyAsCommonResponseWithCode(Create.BadCredential);
}
}
@@ -85,11 +80,11 @@ namespace Timeline.Tests
using (var client = _factory.CreateDefaultClient())
{
var response = await client.PostAsJsonAsync(CreateTokenUrl,
- new CreateTokenRequest { Username = MockUsers.UserUsername, Password = MockUsers.UserPassword });
+ new CreateTokenRequest { Username = MockUser.User.Username, Password = MockUser.User.Password });
var body = response.Should().HaveStatusCodeOk()
.And.Should().HaveBodyAsJson<CreateTokenResponse>().Which;
body.Token.Should().NotBeNullOrWhiteSpace();
- body.User.Should().BeEquivalentTo(MockUsers.UserUserInfo);
+ body.User.Should().BeEquivalentTo(MockUser.User.Info);
}
}
@@ -111,7 +106,7 @@ namespace Timeline.Tests
{
var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = "bad token hahaha" });
response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_BadToken);
+ .And.Should().HaveBodyAsCommonResponseWithCode(Verify.BadFormat);
}
}
@@ -120,18 +115,18 @@ namespace Timeline.Tests
{
using (var client = _factory.CreateDefaultClient())
{
- var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword)).Token;
+ 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(MockUsers.UserUsername, null, null);
+ await userService.PatchUser(MockUser.User.Username, null, null);
}
var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_BadVersion);
+ .And.Should().HaveBodyAsCommonResponseWithCode(Verify.OldVersion);
}
}
@@ -140,50 +135,50 @@ namespace Timeline.Tests
{
using (var client = _factory.CreateDefaultClient())
{
- var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword)).Token;
+ 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.DeleteUser(MockUsers.UserUsername);
+ await userService.DeleteUser(MockUser.User.Username);
}
var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_UserNotExist);
+ .And.Should().HaveBodyAsCommonResponseWithCode(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_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 createTokenResult = await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password);
var response = await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = createTokenResult.Token });
response.Should().HaveStatusCodeOk()
.And.Should().HaveBodyAsJson<VerifyTokenResponse>()
- .Which.User.Should().BeEquivalentTo(MockUsers.UserUserInfo);
+ .Which.User.Should().BeEquivalentTo(MockUser.User.Info);
}
}
}
diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs b/Timeline.Tests/IntegratedTests/UserAvatarTests.cs
index 2a3442d1..439e8d9b 100644
--- a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs
+++ b/Timeline.Tests/IntegratedTests/UserAvatarTests.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,28 +14,27 @@ 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;
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]
@@ -92,7 +91,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().HaveBodyAsCommonResponseWithCode(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch);
}
{
@@ -122,7 +121,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().HaveBodyAsCommonResponseWithCode(ErrorCodes.Http.Common.Header.Missing_ContentLength);
}
{
@@ -130,7 +129,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().HaveBodyAsCommonResponseWithCode(ErrorCodes.Http.Common.Header.Missing_ContentType);
}
{
@@ -139,7 +138,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().HaveBodyAsCommonResponseWithCode(ErrorCodes.Http.Common.Header.Zero_ContentLength);
}
{
diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
index ba15b7ca..d1a67d9d 100644
--- a/Timeline.Tests/IntegratedTests/UserDetailTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
@@ -5,28 +5,27 @@ 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
+ public class UserDetailTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
+ private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
- public UserDetailTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ public UserDetailTest(WebApplicationFactory<Startup> factory)
{
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
}
public void Dispose()
{
- _disposeAction();
+ _testApp.Dispose();
}
[Fact]
@@ -48,7 +47,7 @@ namespace Timeline.Tests.IntegratedTests
async Task GetAndTest(UserDetail d)
{
- var res = await client.GetAsync($"users/{MockUsers.UserUsername}/details");
+ var res = await client.GetAsync($"users/{MockUser.User.Username}/details");
res.Should().HaveStatusCodeOk()
.And.Should().HaveBodyAsJson<UserDetail>()
.Which.Should().BeEquivalentTo(d);
@@ -57,13 +56,13 @@ namespace Timeline.Tests.IntegratedTests
await GetAndTest(new UserDetail());
{
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.AdminUsername}/details", new UserDetail());
+ var res = await client.PatchAsJsonAsync($"users/{MockUser.Admin.Username}/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
+ var res = await client.PatchAsJsonAsync($"users/{MockUser.User.Username}/details", new UserDetail
{
Nickname = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
QQ = "aaaaaaa",
@@ -72,7 +71,7 @@ namespace Timeline.Tests.IntegratedTests
});
var body = res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
.And.Should().HaveBodyAsCommonResponse().Which;
- body.Code.Should().Be(CommonResponse.ErrorCodes.InvalidModel);
+ body.Code.Should().Be(ErrorCodes.Http.Common.InvalidModel);
foreach (var key in new string[] { "nickname", "qq", "email", "phonenumber" })
{
body.Message.Should().ContainEquivalentOf(key);
@@ -89,13 +88,13 @@ namespace Timeline.Tests.IntegratedTests
};
{
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", detail);
+ var res = await client.PatchAsJsonAsync($"users/{MockUser.User.Username}/details", detail);
res.Should().HaveStatusCodeOk();
await GetAndTest(detail);
}
{
- var res = await client.GetAsync($"users/{MockUsers.UserUsername}/nickname");
+ var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
res.Should().HaveStatusCodeOk().And.Should().HaveBodyAsJson<UserDetail>()
.Which.Should().BeEquivalentTo(new UserDetail
{
@@ -111,7 +110,7 @@ namespace Timeline.Tests.IntegratedTests
};
{
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", detail2);
+ var res = await client.PatchAsJsonAsync($"users/{MockUser.User.Username}/details", detail2);
res.Should().HaveStatusCodeOk();
await GetAndTest(new UserDetail
{
@@ -131,7 +130,7 @@ namespace Timeline.Tests.IntegratedTests
using (var client = await _factory.CreateClientAsAdmin())
{
{
- var res = await client.PatchAsJsonAsync($"users/{MockUsers.UserUsername}/details", new UserDetail());
+ var res = await client.PatchAsJsonAsync($"users/{MockUser.User.Username}/details", new UserDetail());
res.Should().HaveStatusCodeOk();
}
diff --git a/Timeline.Tests/UserUnitTest.cs b/Timeline.Tests/IntegratedTests/UserUnitTest.cs
index 77ec37ee..d228c563 100644
--- a/Timeline.Tests/UserUnitTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserUnitTest.cs
@@ -10,23 +10,23 @@ 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
+ public class UserUnitTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
+ private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
- public UserUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ public UserUnitTest(WebApplicationFactory<Startup> factory)
{
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
}
public void Dispose()
{
- _disposeAction();
+ _testApp.Dispose();
}
[Fact]
@@ -36,7 +36,7 @@ namespace Timeline.Tests
{
var res = await client.GetAsync("users");
res.Should().HaveStatusCodeOk().And.Should().HaveBodyAsJson<UserInfo[]>()
- .Which.Should().BeEquivalentTo(MockUsers.UserInfos);
+ .Which.Should().BeEquivalentTo(MockUser.UserInfoList);
}
}
@@ -45,10 +45,10 @@ namespace Timeline.Tests
{
using (var client = await _factory.CreateClientAsAdmin())
{
- var res = await client.GetAsync("users/" + MockUsers.UserUsername);
+ var res = await client.GetAsync("users/" + MockUser.User.Username);
res.Should().HaveStatusCodeOk()
.And.Should().HaveBodyAsJson<UserInfo>()
- .Which.Should().BeEquivalentTo(MockUsers.UserUserInfo);
+ .Which.Should().BeEquivalentTo(MockUser.User.Info);
}
}
@@ -104,13 +104,13 @@ namespace Timeline.Tests
{
using (var client = await _factory.CreateClientAsAdmin())
{
- var res = await client.PutAsJsonAsync("users/" + MockUsers.UserUsername, new UserPutRequest
+ var res = await client.PutAsJsonAsync("users/" + MockUser.User.Username, new UserPutRequest
{
Password = "password",
Administrator = false
});
- res.Should().BePutModified();
- await CheckAdministrator(client, MockUsers.UserUsername, false);
+ res.Should().BePutModify();
+ await CheckAdministrator(client, MockUser.User.Username, false);
}
}
@@ -127,7 +127,7 @@ namespace Timeline.Tests
Password = "password",
Administrator = false
});
- res.Should().BePutCreated();
+ res.Should().BePutCreate();
await CheckAdministrator(client, username, false);
}
}
@@ -149,10 +149,10 @@ namespace Timeline.Tests
using (var client = await _factory.CreateClientAsAdmin())
{
{
- var res = await client.PatchAsJsonAsync("users/" + MockUsers.UserUsername,
+ var res = await client.PatchAsJsonAsync("users/" + MockUser.User.Username,
new UserPatchRequest { Administrator = false });
res.Should().HaveStatusCodeOk();
- await CheckAdministrator(client, MockUsers.UserUsername, false);
+ await CheckAdministrator(client, MockUser.User.Username, false);
}
}
}
@@ -163,9 +163,9 @@ namespace Timeline.Tests
using (var client = await _factory.CreateClientAsAdmin())
{
{
- var url = "users/" + MockUsers.UserUsername;
+ var url = "users/" + MockUser.User.Username;
var res = await client.DeleteAsync(url);
- res.Should().BeDeleteDeleted();
+ res.Should().BeDeleteDelete();
var res2 = await client.GetAsync(url);
res2.Should().HaveStatusCodeNotFound();
@@ -186,21 +186,22 @@ namespace Timeline.Tests
}
- public class ChangeUsernameUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
+ public class ChangeUsernameUnitTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
private const string url = "userop/changeusername";
+ private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
- public ChangeUsernameUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ public ChangeUsernameUnitTest(WebApplicationFactory<Startup> factory)
{
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
}
public void Dispose()
{
- _disposeAction();
+ _testApp.Dispose();
}
[Fact]
@@ -210,10 +211,10 @@ namespace Timeline.Tests
{
// missing old username
await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangeUsernameRequest { OldUsername= null, NewUsername= "hhh" });
+ new ChangeUsernameRequest { OldUsername = null, NewUsername = "hhh" });
// missing new username
await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
- new ChangeUsernameRequest { OldUsername= "hhh", NewUsername= null });
+ new ChangeUsernameRequest { OldUsername = "hhh", NewUsername = null });
// bad username
await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
new ChangeUsernameRequest { OldUsername = "hhh", NewUsername = "???" });
@@ -226,7 +227,7 @@ namespace Timeline.Tests
using (var client = await _factory.CreateClientAsAdmin())
{
var res = await client.PostAsJsonAsync(url,
- new ChangeUsernameRequest{ OldUsername= "usernotexist", NewUsername= "newUsername" });
+ new ChangeUsernameRequest { OldUsername = "usernotexist", NewUsername = "newUsername" });
res.Should().HaveStatusCodeBadRequest()
.And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.ChangeUsername_NotExist);
}
@@ -238,7 +239,7 @@ namespace Timeline.Tests
using (var client = await _factory.CreateClientAsAdmin())
{
var res = await client.PostAsJsonAsync(url,
- new ChangeUsernameRequest { OldUsername = MockUsers.UserUsername, NewUsername = MockUsers.AdminUsername });
+ new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = MockUser.Admin.Username });
res.Should().HaveStatusCodeBadRequest()
.And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.ChangeUsername_AlreadyExist);
}
@@ -251,29 +252,30 @@ namespace Timeline.Tests
{
const string newUsername = "hahaha";
var res = await client.PostAsJsonAsync(url,
- new ChangeUsernameRequest { OldUsername = MockUsers.UserUsername, NewUsername = newUsername });
+ new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = newUsername });
res.Should().HaveStatusCodeOk();
- await client.CreateUserTokenAsync(newUsername, MockUsers.UserPassword);
+ await client.CreateUserTokenAsync(newUsername, MockUser.User.Password);
}
}
}
- public class ChangePasswordUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
+ public class ChangePasswordUnitTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
private const string url = "userop/changepassword";
+ private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- private readonly Action _disposeAction;
- public ChangePasswordUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ public ChangePasswordUnitTest(WebApplicationFactory<Startup> factory)
{
- _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
}
public void Dispose()
{
- _disposeAction();
+ _testApp.Dispose();
}
[Fact]
@@ -308,9 +310,9 @@ namespace Timeline.Tests
{
const string newPassword = "new";
var res = await client.PostAsJsonAsync(url,
- new ChangePasswordRequest { OldPassword = MockUsers.UserPassword, NewPassword = newPassword });
+ new ChangePasswordRequest { OldPassword = MockUser.User.Password, NewPassword = newPassword });
res.Should().HaveStatusCodeOk();
- await client.CreateUserTokenAsync(MockUsers.UserUsername, newPassword);
+ await client.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..bc2df469 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 = UserUtility.IsAdminToRoleString(user.Administrator),
+ Avatar = UserAvatar.Create(DateTime.Now)
+ };
+ }
+
+ 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/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj
index 1852da5f..36bc03bc 100644
--- a/Timeline.Tests/Timeline.Tests.csproj
+++ b/Timeline.Tests/Timeline.Tests.csproj
@@ -14,6 +14,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="Moq" Version="4.13.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
diff --git a/Timeline.Tests/UserAvatarServiceTest.cs b/Timeline.Tests/UserAvatarServiceTest.cs
index 93bb70ae..d22ad113 100644
--- a/Timeline.Tests/UserAvatarServiceTest.cs
+++ b/Timeline.Tests/UserAvatarServiceTest.cs
@@ -1,6 +1,7 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using SixLabors.ImageSharp.Formats.Png;
using System;
using System.Linq;
@@ -151,28 +152,26 @@ namespace Timeline.Tests
private readonly MockDefaultUserAvatarProvider _mockDefaultUserAvatarProvider;
- 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, MockUserAvatarValidator mockUserAvatarValidator)
{
_mockDefaultUserAvatarProvider = mockDefaultUserAvatarProvider;
- _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, _mockDefaultUserAvatarProvider, mockUserAvatarValidator, _eTagGenerator);
}
+
public void Dispose()
{
- _loggerFactory.Dispose();
_database.Dispose();
}
@@ -197,14 +196,14 @@ namespace Timeline.Tests
[Fact]
public async Task GetAvatarETag_ShouldReturn_Default()
{
- const string username = MockUsers.UserUsername;
+ string username = MockUser.User.Username;
(await _service.GetAvatarETag(username)).Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatarETag()));
}
[Fact]
public async Task GetAvatarETag_ShouldReturn_Data()
{
- const string username = MockUsers.UserUsername;
+ string username = MockUser.User.Username;
{
// create mock data
var context = _database.DatabaseContext;
@@ -237,14 +236,14 @@ namespace Timeline.Tests
[Fact]
public async Task GetAvatar_ShouldReturn_Default()
{
- const string username = MockUsers.UserUsername;
+ string username = MockUser.User.Username;
(await _service.GetAvatar(username)).Avatar.Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatar()).Avatar);
}
[Fact]
public async Task GetAvatar_ShouldReturn_Data()
{
- const string username = MockUsers.UserUsername;
+ string username = MockUser.User.Username;
{
// create mock data
@@ -287,7 +286,7 @@ namespace Timeline.Tests
[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();
diff --git a/Timeline.Tests/UserDetailServiceTest.cs b/Timeline.Tests/UserDetailServiceTest.cs
index 98613429..d16d1a40 100644
--- a/Timeline.Tests/UserDetailServiceTest.cs
+++ b/Timeline.Tests/UserDetailServiceTest.cs
@@ -1,5 +1,5 @@
using FluentAssertions;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Linq;
using System.Threading.Tasks;
@@ -9,28 +9,24 @@ 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)
+ public UserDetailServiceTest()
{
- _loggerFactory = Logging.Create(outputHelper);
_database = new TestDatabase();
- _service = new UserDetailService(_loggerFactory.CreateLogger<UserDetailService>(), _database.DatabaseContext);
+ _service = new UserDetailService(NullLogger<UserDetailService>.Instance, _database.DatabaseContext);
}
public void Dispose()
{
- _loggerFactory.Dispose();
_database.Dispose();
}
@@ -56,13 +52,13 @@ namespace Timeline.Tests
public async Task GetNickname_Should_Create_And_ReturnDefault()
{
{
- var nickname = await _service.GetUserNickname(MockUsers.UserUsername);
+ var nickname = await _service.GetUserNickname(MockUser.User.Username);
nickname.Should().BeNull();
}
{
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var detail = context.UserDetails.Where(e => e.UserId == userId).Single();
detail.Nickname.Should().BeNullOrEmpty();
detail.QQ.Should().BeNullOrEmpty();
@@ -80,7 +76,7 @@ namespace Timeline.Tests
{
{
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var entity = new UserDetailEntity
{
Nickname = nickname,
@@ -91,7 +87,7 @@ namespace Timeline.Tests
}
{
- var n = await _service.GetUserNickname(MockUsers.UserUsername);
+ var n = await _service.GetUserNickname(MockUser.User.Username);
n.Should().Equals(string.IsNullOrEmpty(nickname) ? null : nickname);
}
}
@@ -118,13 +114,13 @@ namespace Timeline.Tests
public async Task GetDetail_Should_Create_And_ReturnDefault()
{
{
- var detail = await _service.GetUserDetail(MockUsers.UserUsername);
+ var detail = await _service.GetUserDetail(MockUser.User.Username);
detail.Should().BeEquivalentTo(new UserDetail());
}
{
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var detail = context.UserDetails.Where(e => e.UserId == userId).Single();
detail.Nickname.Should().BeNullOrEmpty();
detail.QQ.Should().BeNullOrEmpty();
@@ -143,7 +139,7 @@ namespace Timeline.Tests
{
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var entity = new UserDetailEntity
{
Email = email,
@@ -155,7 +151,7 @@ namespace Timeline.Tests
}
{
- var detail = await _service.GetUserDetail(MockUsers.UserUsername);
+ var detail = await _service.GetUserDetail(MockUser.User.Username);
detail.Should().BeEquivalentTo(new UserDetail
{
Email = email,
@@ -187,10 +183,10 @@ namespace Timeline.Tests
[Fact]
public async Task UpdateDetail_Empty_Should_Work()
{
- await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail());
+ await _service.UpdateUserDetail(MockUser.User.Username, new UserDetail());
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var entity = context.UserDetails.Where(e => e.UserId == userId).Single();
entity.Nickname.Should().BeNullOrEmpty();
entity.QQ.Should().BeNullOrEmpty();
@@ -215,10 +211,10 @@ namespace Timeline.Tests
return detail;
}
- await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(mockData1));
+ await _service.UpdateUserDetail(MockUser.User.Username, CreateWith(mockData1));
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var entity = context.UserDetails.Where(e => e.UserId == userId).Single();
void TestWith(string propertyValue)
@@ -230,9 +226,9 @@ namespace Timeline.Tests
TestWith(mockData1);
- await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(mockData2));
+ await _service.UpdateUserDetail(MockUser.User.Username, CreateWith(mockData2));
TestWith(mockData2);
- await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(""));
+ await _service.UpdateUserDetail(MockUser.User.Username, CreateWith(""));
TestWith("");
}
@@ -247,10 +243,10 @@ namespace Timeline.Tests
Description = "aaaaaaaaaa"
};
- await _service.UpdateUserDetail(MockUsers.UserUsername, detail);
+ await _service.UpdateUserDetail(MockUser.User.Username, detail);
var context = _database.DatabaseContext;
- var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername);
+ var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUser.User.Username);
var entity = context.UserDetails.Where(e => e.UserId == userId).Single();
entity.QQ.Should().Equals(detail.QQ);
entity.Email.Should().Equals(detail.Email);
@@ -265,7 +261,7 @@ namespace Timeline.Tests
Description = "bbbbbbbbb"
};
- await _service.UpdateUserDetail(MockUsers.UserUsername, detail2);
+ await _service.UpdateUserDetail(MockUser.User.Username, detail2);
entity.QQ.Should().Equals(detail.QQ);
entity.Email.Should().Equals(detail2.Email);
entity.PhoneNumber.Should().BeNullOrEmpty();
diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs
index 3c166448..2e661695 100644
--- a/Timeline/Controllers/TokenController.cs
+++ b/Timeline/Controllers/TokenController.cs
@@ -3,39 +3,42 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
using Timeline.Services;
-using static Timeline.Helpers.MyLogHelper;
+using Timeline.Helpers;
-namespace Timeline.Controllers
+namespace Timeline
{
- [Route("token")]
- [ApiController]
- public class TokenController : Controller
+ public static partial class ErrorCodes
{
- private static class LoggingEventIds
- {
- public const int CreateSucceeded = 1000;
- public const int CreateFailed = 1001;
-
- public const int VerifySucceeded = 2000;
- public const int VerifyFailed = 2001;
- }
-
- public static class ErrorCodes
+ public static partial class Http
{
- public const int Create_UserNotExist = -1001;
- public const int Create_BadPassword = -1002;
- public const int Create_BadExpireOffset = -1003;
+ public static class Token // bbb = 001
+ {
+ public static class Create // cc = 01
+ {
+ public const int BadCredential = 10010101;
+ }
- public const int Verify_BadToken = -2001;
- public const int Verify_UserNotExist = -2002;
- public const int Verify_BadVersion = -2003;
- public const int Verify_Expired = -2004;
+ public static class Verify // cc = 02
+ {
+ public const int BadFormat = 10010201;
+ public const int UserNotExist = 10010202;
+ public const int OldVersion = 10010203;
+ public const int Expired = 10010204;
+ }
+ }
}
+ }
+}
+namespace Timeline.Controllers
+{
+ [Route("token")]
+ [ApiController]
+ public class TokenController : Controller
+ {
private readonly IUserService _userService;
private readonly ILogger<TokenController> _logger;
private readonly IClock _clock;
@@ -51,23 +54,28 @@ namespace Timeline.Controllers
[AllowAnonymous]
public async Task<IActionResult> Create([FromBody] CreateTokenRequest request)
{
- void LogFailure(string reason, int code, Exception e = null)
+ void LogFailure(string reason, Exception e = null)
{
- _logger.LogInformation(LoggingEventIds.CreateFailed, e, FormatLogMessage("Attemp to login failed.",
- Pair("Reason", reason),
- Pair("Code", code),
- Pair("Username", request.Username),
- Pair("Password", request.Password),
- Pair("Expire Offset (in days)", request.ExpireOffset)));
+ _logger.LogInformation(e, Log.Format("Attemp to login failed.",
+ ("Reason", reason),
+ ("Username", request.Username),
+ ("Password", request.Password),
+ ("Expire (in days)", request.Expire)
+ ));
}
try
{
- var expiredTime = request.ExpireOffset == null ? null : (DateTime?)(_clock.GetCurrentTime().AddDays(request.ExpireOffset.Value));
- var result = await _userService.CreateToken(request.Username, request.Password, expiredTime);
- _logger.LogInformation(LoggingEventIds.CreateSucceeded, FormatLogMessage("Attemp to login succeeded.",
- Pair("Username", request.Username),
- Pair("Expire Time", expiredTime == null ? "default" : expiredTime.Value.ToString())));
+ DateTime? expireTime = null;
+ if (request.Expire != null)
+ expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value);
+
+ var result = await _userService.CreateToken(request.Username, request.Password, expireTime);
+
+ _logger.LogInformation(Log.Format("Attemp to login succeeded.",
+ ("Username", request.Username),
+ ("Expire At", expireTime?.ToString() ?? "default")
+ ));
return Ok(new CreateTokenResponse
{
Token = result.Token,
@@ -76,15 +84,15 @@ namespace Timeline.Controllers
}
catch (UserNotExistException e)
{
- var code = ErrorCodes.Create_UserNotExist;
- LogFailure("User does not exist.", code, e);
- return BadRequest(new CommonResponse(code, "Bad username or password."));
+ LogFailure("User does not exist.", e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Create.BadCredential,
+ "Bad username or password."));
}
catch (BadPasswordException e)
{
- var code = ErrorCodes.Create_BadPassword;
- LogFailure("Password is wrong.", code, e);
- return BadRequest(new CommonResponse(code, "Bad username or password."));
+ LogFailure("Password is wrong.", e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Create.BadCredential,
+ "Bad username or password."));
}
}
@@ -92,22 +100,20 @@ namespace Timeline.Controllers
[AllowAnonymous]
public async Task<IActionResult> Verify([FromBody] VerifyTokenRequest request)
{
- void LogFailure(string reason, int code, Exception e = null, params KeyValuePair<string, object>[] otherProperties)
+ void LogFailure(string reason, Exception e = null, params (string, object)[] otherProperties)
{
- var properties = new KeyValuePair<string, object>[3 + otherProperties.Length];
- properties[0] = Pair("Reason", reason);
- properties[1] = Pair("Code", code);
- properties[2] = Pair("Token", request.Token);
- otherProperties.CopyTo(properties, 3);
- _logger.LogInformation(LoggingEventIds.VerifyFailed, e, FormatLogMessage("Token verification failed.", properties));
+ var properties = new (string, object)[2 + otherProperties.Length];
+ properties[0] = ("Reason", reason);
+ properties[1] = ("Token", request.Token);
+ otherProperties.CopyTo(properties, 2);
+ _logger.LogInformation(e, Log.Format("Token verification failed.", properties));
}
try
{
var result = await _userService.VerifyToken(request.Token);
- _logger.LogInformation(LoggingEventIds.VerifySucceeded,
- FormatLogMessage("Token verification succeeded.",
- Pair("Username", result.Username), Pair("Token", request.Token)));
+ _logger.LogInformation(Log.Format("Token verification succeeded.",
+ ("Username", result.Username), ("Token", request.Token)));
return Ok(new VerifyTokenResponse
{
User = result
@@ -118,33 +124,28 @@ namespace Timeline.Controllers
if (e.ErrorCode == JwtTokenVerifyException.ErrorCodes.Expired)
{
const string message = "Token is expired.";
- var code = ErrorCodes.Verify_Expired;
var innerException = e.InnerException as SecurityTokenExpiredException;
- LogFailure(message, code, e, Pair("Expires", innerException.Expires));
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(message, e, ("Expires", innerException.Expires), ("Current Time", _clock.GetCurrentTime()));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.Expired, message));
}
else
{
const string message = "Token is of bad format.";
- var code = ErrorCodes.Verify_BadToken;
- LogFailure(message, code, e);
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(message, e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.BadFormat, message));
}
}
catch (UserNotExistException e)
{
const string message = "User does not exist. Administrator might have deleted this user.";
- var code = ErrorCodes.Verify_UserNotExist;
- LogFailure(message, code, e);
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(message, e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.UserNotExist, message));
}
catch (BadTokenVersionException e)
{
- const string message = "Token has a old version.";
- var code = ErrorCodes.Verify_BadVersion;
- LogFailure(message, code, e);
- _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because version is old. Code: {} Token: {}.", code, request.Token);
- return BadRequest(new CommonResponse(code, message));
+ const string message = "Token has an old version.";
+ LogFailure(message, e, ("Token Version", e.TokenVersion), ("Required Version", e.RequiredVersion));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.OldVersion, message));
}
}
}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index bd13f0a3..c0cd3cdb 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -65,10 +65,10 @@ namespace Timeline.Controllers
{
case PutResult.Created:
_logger.LogInformation(FormatLogMessage("A user is created.", Pair("Username", username)));
- return CreatedAtAction("Get", new { username }, CommonPutResponse.Created);
+ return CreatedAtAction("Get", new { username }, CommonPutResponse.Create());
case PutResult.Modified:
_logger.LogInformation(FormatLogMessage("A user is modified.", Pair("Username", username)));
- return Ok(CommonPutResponse.Modified);
+ return Ok(CommonPutResponse.Modify());
default:
throw new Exception("Unreachable code.");
}
@@ -102,12 +102,12 @@ namespace Timeline.Controllers
{
await _userService.DeleteUser(username);
_logger.LogInformation(FormatLogMessage("A user is deleted.", Pair("Username", username)));
- return Ok(CommonDeleteResponse.Deleted);
+ return Ok(CommonDeleteResponse.Delete());
}
catch (UserNotExistException e)
{
_logger.LogInformation(e, FormatLogMessage("Attempt to delete a non-existent user.", Pair("Username", username)));
- return Ok(CommonDeleteResponse.NotExists);
+ return Ok(CommonDeleteResponse.NotExist());
}
}
diff --git a/Timeline/ErrorCodes.cs b/Timeline/ErrorCodes.cs
new file mode 100644
index 00000000..0b325e27
--- /dev/null
+++ b/Timeline/ErrorCodes.cs
@@ -0,0 +1,29 @@
+namespace Timeline
+{
+ /// <summary>
+ /// All error code constants.
+ /// </summary>
+ /// <remarks>
+ /// Scheme:
+ /// abbbccdd
+ /// </remarks>
+ public static partial class ErrorCodes
+ {
+ public static partial class Http // a = 1
+ {
+ public static class Common // bbb = 000
+ {
+ public const int InvalidModel = 10000000;
+
+ public static class Header // cc = 01
+ {
+ public const int Missing_ContentType = 10010101; // dd = 01
+ public const int Missing_ContentLength = 10010102; // dd = 02
+ public const int Zero_ContentLength = 10010103; // dd = 03
+ public const int BadFormat_IfNonMatch = 10010104; // dd = 04
+ }
+ }
+ }
+
+ }
+}
diff --git a/Timeline/Helpers/Log.cs b/Timeline/Helpers/Log.cs
index 123e8a8e..64391cd1 100644
--- a/Timeline/Helpers/Log.cs
+++ b/Timeline/Helpers/Log.cs
@@ -3,6 +3,7 @@ using System.Text;
namespace Timeline.Helpers
{
+ // TODO! Remember to remove this after refactor.
public static class MyLogHelper
{
public static KeyValuePair<string, object> Pair(string key, object value) => new KeyValuePair<string, object>(key, value);
@@ -21,4 +22,22 @@ namespace Timeline.Helpers
return builder.ToString();
}
}
+
+ public static class Log
+ {
+ public static string Format(string summary, params (string, object)[] properties)
+ {
+ var builder = new StringBuilder();
+ builder.Append(summary);
+ foreach (var property in properties)
+ {
+ var (key, value) = property;
+ builder.AppendLine();
+ builder.Append(key);
+ builder.Append(" : ");
+ builder.Append(value);
+ }
+ return builder.ToString();
+ }
+ }
}
diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs
index a72f187c..af185e85 100644
--- a/Timeline/Models/Http/Common.cs
+++ b/Timeline/Models/Http/Common.cs
@@ -2,43 +2,29 @@ namespace Timeline.Models.Http
{
public class CommonResponse
{
- public static class ErrorCodes
- {
- /// <summary>
- /// Used when the model is invaid.
- /// For example a required field is null.
- /// </summary>
- public const int InvalidModel = -100;
-
- public const int Header_Missing_ContentType = -111;
- public const int Header_Missing_ContentLength = -112;
- public const int Header_Zero_ContentLength = -113;
- public const int Header_BadFormat_IfNonMatch = -114;
- }
-
public static CommonResponse InvalidModel(string message)
{
- return new CommonResponse(ErrorCodes.InvalidModel, message);
+ return new CommonResponse(ErrorCodes.Http.Common.InvalidModel, message);
}
public static CommonResponse MissingContentType()
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentType, "Header Content-Type is required.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentType, "Header Content-Type is required.");
}
public static CommonResponse MissingContentLength()
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentLength, "Header Content-Length is missing or of bad format.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentLength, "Header Content-Length is missing or of bad format.");
}
public static CommonResponse ZeroContentLength()
{
- return new CommonResponse(ErrorCodes.Header_Zero_ContentLength, "Header Content-Length must not be 0.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Zero_ContentLength, "Header Content-Length must not be 0.");
}
public static CommonResponse BadIfNonMatch()
{
- return new CommonResponse(ErrorCodes.Header_BadFormat_IfNonMatch, "Header If-Non-Match is of bad format.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch, "Header If-Non-Match is of bad format.");
}
public CommonResponse()
@@ -56,21 +42,55 @@ namespace Timeline.Models.Http
public string Message { get; set; }
}
+ public class CommonDataResponse<T> : CommonResponse
+ {
+ public CommonDataResponse()
+ {
+
+ }
+
+ public CommonDataResponse(int code, string message, T data)
+ : base(code, message)
+ {
+ Data = data;
+ }
+
+ public T Data { get; set; }
+ }
+
public static class CommonPutResponse
{
- public const int CreatedCode = 0;
- public const int ModifiedCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool create)
+ {
+ Create = create;
+ }
- public static CommonResponse Created { get; } = new CommonResponse(CreatedCode, "A new item is created.");
- public static CommonResponse Modified { get; } = new CommonResponse(ModifiedCode, "An existent item is modified.");
+ public bool Create { get; set; }
+ }
+
+ public static CommonDataResponse<ResponseData> Create() =>
+ new CommonDataResponse<ResponseData>(0, "A new item is created.", new ResponseData(true));
+ public static CommonDataResponse<ResponseData> Modify() =>
+ new CommonDataResponse<ResponseData>(0, "An existent item is modified.", new ResponseData(false));
}
public static class CommonDeleteResponse
{
- public const int DeletedCode = 0;
- public const int NotExistsCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool delete)
+ {
+ Delete = delete;
+ }
+
+ public bool Delete { get; set; }
+ }
- public static CommonResponse Deleted { get; } = new CommonResponse(DeletedCode, "An existent item is deleted.");
- public static CommonResponse NotExists { get; } = new CommonResponse(NotExistsCode, "The item does not exist.");
+ public static CommonDataResponse<ResponseData> Delete() =>
+ new CommonDataResponse<ResponseData>(0, "An existent item is deleted.", new ResponseData(true));
+ public static CommonDataResponse<ResponseData> NotExist() =>
+ new CommonDataResponse<ResponseData>(0, "The item does not exist.", new ResponseData(false));
}
}
diff --git a/Timeline/Models/Http/Token.cs b/Timeline/Models/Http/Token.cs
index 68a66d0a..615b6d8a 100644
--- a/Timeline/Models/Http/Token.cs
+++ b/Timeline/Models/Http/Token.cs
@@ -10,7 +10,7 @@ namespace Timeline.Models.Http
public string Password { get; set; }
// in days, optional
[Range(1, 365)]
- public int? ExpireOffset { get; set; }
+ public int? Expire { get; set; }
}
public class CreateTokenResponse