diff options
35 files changed, 523 insertions, 147 deletions
diff --git a/CI/build-pipeline.yml b/CI/build-pipeline.yml index 2c15e301..85b373ff 100644 --- a/CI/build-pipeline.yml +++ b/CI/build-pipeline.yml @@ -19,7 +19,7 @@ steps: dotnet restore Timeline.Tests/Timeline.Tests.csproj --configfile nuget.config
displayName: Dotnet Restore
-- script: dotnet test Timeline.Tests/Timeline.Tests.csproj --configuration $(buildConfiguration) --no-restore --logger trx --collect:"XPlat Code Coverage"
+- script: dotnet test Timeline.Tests/Timeline.Tests.csproj --configuration $(buildConfiguration) --no-restore --logger trx --collect:"XPlat Code Coverage" --settings './coverletArgs.runsettings'
displayName: Dotnet Test
- task: PublishTestResults@2
diff --git a/Timeline.Tests/AuthorizationUnitTest.cs b/Timeline.Tests/AuthorizationUnitTest.cs index 8df23c45..d9fb7406 100644 --- a/Timeline.Tests/AuthorizationUnitTest.cs +++ b/Timeline.Tests/AuthorizationUnitTest.cs @@ -34,7 +34,7 @@ namespace Timeline.Tests [Fact]
public async Task AuthenticationTest()
{
- using (var client = await _factory.CreateClientWithUser("user", "user"))
+ using (var client = await _factory.CreateClientAsUser())
{
var response = await client.GetAsync(AuthorizeUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -44,7 +44,7 @@ namespace Timeline.Tests [Fact]
public async Task UserAuthorizationTest()
{
- using (var client = await _factory.CreateClientWithUser("user", "user"))
+ using (var client = await _factory.CreateClientAsUser())
{
var response1 = await client.GetAsync(UserUrl);
Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
@@ -56,7 +56,7 @@ namespace Timeline.Tests [Fact]
public async Task AdminAuthorizationTest()
{
- using (var client = await _factory.CreateClientWithUser("admin", "admin"))
+ using (var client = await _factory.CreateClientAsAdmin())
{
var response1 = await client.GetAsync(UserUrl);
Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs index c8bec266..8a44c852 100644 --- a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs +++ b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs @@ -2,7 +2,8 @@ using Microsoft.AspNetCore.Mvc.Testing; using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks;
-using Timeline.Entities.Http;
+using Timeline.Models.Http;
+using Timeline.Tests.Mock.Data;
namespace Timeline.Tests.Helpers.Authentication
{
@@ -10,19 +11,30 @@ namespace Timeline.Tests.Helpers.Authentication {
private const string CreateTokenUrl = "/token/create";
- public static async Task<CreateTokenResponse> CreateUserTokenAsync(this HttpClient client, string username, string password, double? expireOffset = null)
+ public static async Task<CreateTokenResponse> CreateUserTokenAsync(this HttpClient client, string username, string password, int? expireOffset = null)
{
var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, ExpireOffset = expireOffset });
+ response.AssertOk();
var result = JsonConvert.DeserializeObject<CreateTokenResponse>(await response.Content.ReadAsStringAsync());
return result;
}
- public static async Task<HttpClient> CreateClientWithUser<T>(this WebApplicationFactory<T> factory, string username, string password) where T : class
+ public static async Task<HttpClient> CreateClientWithCredential<T>(this WebApplicationFactory<T> factory, string username, string password) where T : class
{
var client = factory.CreateDefaultClient();
var token = (await client.CreateUserTokenAsync(username, password)).Token;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
return client;
}
+
+ public static Task<HttpClient> CreateClientAsUser<T>(this WebApplicationFactory<T> factory) where T : class
+ {
+ return factory.CreateClientWithCredential(MockUsers.UserUsername, MockUsers.UserPassword);
+ }
+
+ public static Task<HttpClient> CreateClientAsAdmin<T>(this WebApplicationFactory<T> factory) where T : class
+ {
+ return factory.CreateClientWithCredential(MockUsers.AdminUsername, MockUsers.AdminPassword);
+ }
}
}
diff --git a/Timeline.Tests/Helpers/HttpClientExtensions.cs b/Timeline.Tests/Helpers/HttpClientExtensions.cs new file mode 100644 index 00000000..cd40d91e --- /dev/null +++ b/Timeline.Tests/Helpers/HttpClientExtensions.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Timeline.Tests.Helpers
+{
+ public static class HttpClientExtensions
+ {
+ public static Task<HttpResponseMessage> PatchAsJsonAsync<T>(this HttpClient client, string url, T body)
+ {
+ return client.PatchAsync(url, new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"));
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs b/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs new file mode 100644 index 00000000..1c079d0e --- /dev/null +++ b/Timeline.Tests/Helpers/InvalidModelTestHelpers.cs @@ -0,0 +1,27 @@ +using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Xunit;
+
+namespace Timeline.Tests.Helpers
+{
+ public static class InvalidModelTestHelpers
+ {
+ public static async Task TestPostInvalidModel<T>(HttpClient client, string url, T body)
+ {
+ var response = await client.PostAsJsonAsync(url, body);
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ var responseBody = await response.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(CommonResponse.ErrorCodes.InvalidModel, responseBody.Code);
+ }
+
+ public static async Task TestPutInvalidModel<T>(HttpClient client, string url, T body)
+ {
+ var response = await client.PutAsJsonAsync(url, body);
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ var responseBody = await response.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(CommonResponse.ErrorCodes.InvalidModel, responseBody.Code);
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs index d9503526..b49756e4 100644 --- a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs +++ b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs @@ -5,8 +5,10 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Timeline.Models;
+using Timeline.Entities;
using Timeline.Services;
+using Timeline.Tests.Mock.Data;
+using Timeline.Tests.Mock.Services;
using Xunit.Abstractions;
namespace Timeline.Tests.Helpers
@@ -34,7 +36,7 @@ namespace Timeline.Tests.Helpers using (var context = new DatabaseContext(options))
{
context.Database.EnsureCreated();
- context.Users.AddRange(TestMockUsers.MockUsers);
+ context.Users.AddRange(MockUsers.Users);
context.SaveChanges();
}
}
diff --git a/Timeline.Tests/Helpers/ResponseExtensions.cs b/Timeline.Tests/Helpers/ResponseExtensions.cs index 155836fb..46c9e81d 100644 --- a/Timeline.Tests/Helpers/ResponseExtensions.cs +++ b/Timeline.Tests/Helpers/ResponseExtensions.cs @@ -1,11 +1,58 @@ using Newtonsoft.Json;
+using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Xunit;
namespace Timeline.Tests.Helpers
{
public static class ResponseExtensions
{
+ public static void AssertOk(this HttpResponseMessage response)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ public static void AssertNotFound(this HttpResponseMessage response)
+ {
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ public static void AssertBadRequest(this HttpResponseMessage response)
+ {
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ public static async Task AssertIsPutCreated(this HttpResponseMessage response)
+ {
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ var body = await response.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(CommonPutResponse.CreatedCode, body.Code);
+ }
+
+ public static async Task AssertIsPutModified(this HttpResponseMessage response)
+ {
+ response.AssertOk();
+ var body = await response.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(CommonPutResponse.ModifiedCode, body.Code);
+ }
+
+
+ public static async Task AssertIsDeleteDeleted(this HttpResponseMessage response)
+ {
+ response.AssertOk();
+ var body = await response.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(CommonDeleteResponse.DeletedCode, body.Code);
+ }
+
+ public static async Task AssertIsDeleteNotExist(this HttpResponseMessage response)
+ {
+ response.AssertOk();
+ var body = await response.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(CommonDeleteResponse.NotExistsCode, body.Code);
+ }
+
public static async Task<T> ReadBodyAsJson<T>(this HttpResponseMessage response)
{
return JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
diff --git a/Timeline.Tests/Helpers/TestUsers.cs b/Timeline.Tests/Helpers/TestUsers.cs deleted file mode 100644 index 71de8237..00000000 --- a/Timeline.Tests/Helpers/TestUsers.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic;
-using System.Linq;
-using Timeline.Entities;
-using Timeline.Models;
-using Timeline.Services;
-
-namespace Timeline.Tests.Helpers
-{
- public static class TestMockUsers
- {
- static TestMockUsers()
- {
- var mockUsers = new List<User>();
- var passwordService = new PasswordService();
-
- mockUsers.Add(new User
- {
- Name = "user",
- EncryptedPassword = passwordService.HashPassword("user"),
- RoleString = UserUtility.IsAdminToRoleString(false),
- Version = 0,
- });
- mockUsers.Add(new User
- {
- Name = "admin",
- EncryptedPassword = passwordService.HashPassword("admin"),
- RoleString = UserUtility.IsAdminToRoleString(true),
- Version = 0,
- });
-
- MockUsers = mockUsers;
-
- var mockUserInfos = mockUsers.Select(u => UserUtility.CreateUserInfo(u)).ToList();
- mockUserInfos.Sort(UserInfoComparers.Comparer);
- MockUserInfos = mockUserInfos;
- }
-
- public static List<User> MockUsers { get; }
-
- public static IReadOnlyList<UserInfo> MockUserInfos { get; }
- }
-}
diff --git a/Timeline.Tests/Helpers/UserInfoComparers.cs b/Timeline.Tests/Helpers/UserInfoComparers.cs index c7c584b7..1a1c652d 100644 --- a/Timeline.Tests/Helpers/UserInfoComparers.cs +++ b/Timeline.Tests/Helpers/UserInfoComparers.cs @@ -1,5 +1,5 @@ using System.Collections.Generic;
-using Timeline.Entities;
+using Timeline.Models;
namespace Timeline.Tests.Helpers
{
diff --git a/Timeline.Tests/Mock/Data/TestUsers.cs b/Timeline.Tests/Mock/Data/TestUsers.cs new file mode 100644 index 00000000..d784e48c --- /dev/null +++ b/Timeline.Tests/Mock/Data/TestUsers.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic;
+using System.Linq;
+using Timeline.Entities;
+using Timeline.Models;
+using Timeline.Services;
+using Timeline.Tests.Helpers;
+
+namespace Timeline.Tests.Mock.Data
+{
+ public static class MockUsers
+ {
+ static MockUsers()
+ {
+ var mockUsers = new List<User>();
+ var passwordService = new PasswordService();
+
+ mockUsers.Add(new User
+ {
+ Name = UserUsername,
+ EncryptedPassword = passwordService.HashPassword(UserPassword),
+ RoleString = UserUtility.IsAdminToRoleString(false),
+ Version = 0,
+ });
+ mockUsers.Add(new User
+ {
+ Name = AdminUsername,
+ EncryptedPassword = passwordService.HashPassword(AdminPassword),
+ RoleString = UserUtility.IsAdminToRoleString(true),
+ Version = 0,
+ });
+
+ Users = mockUsers;
+
+ var mockUserInfos = mockUsers.Select(u => UserUtility.CreateUserInfo(u)).ToList();
+ UserUserInfo = mockUserInfos[0];
+ AdminUserInfo = mockUserInfos[1];
+ mockUserInfos.Sort(UserInfoComparers.Comparer);
+ UserInfos = mockUserInfos;
+ }
+
+ public const string UserUsername = "user";
+ public const string AdminUsername = "admin";
+ public const string UserPassword= "user";
+ public const string AdminPassword = "admin";
+
+ internal static IReadOnlyList<User> Users { get; }
+ public static IReadOnlyList<UserInfo> UserInfos { get; }
+
+ public static UserInfo AdminUserInfo { get; }
+ public static UserInfo UserUserInfo { get; }
+ }
+}
diff --git a/Timeline.Tests/Helpers/TestClock.cs b/Timeline.Tests/Mock/Services/TestClock.cs index ea90305f..0082171e 100644 --- a/Timeline.Tests/Helpers/TestClock.cs +++ b/Timeline.Tests/Mock/Services/TestClock.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using System;
using Timeline.Services;
-namespace Timeline.Tests.Helpers
+namespace Timeline.Tests.Mock.Services
{
public class TestClock : IClock
{
diff --git a/Timeline.Tests/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj index 8cc304f4..854c63ac 100644 --- a/Timeline.Tests/Timeline.Tests.csproj +++ b/Timeline.Tests/Timeline.Tests.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
diff --git a/Timeline.Tests/TokenUnitTest.cs b/Timeline.Tests/TokenUnitTest.cs index f942767d..5f3b8e6d 100644 --- a/Timeline.Tests/TokenUnitTest.cs +++ b/Timeline.Tests/TokenUnitTest.cs @@ -2,14 +2,15 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
-using System.Linq;
using System.Net;
using System.Net.Http;
using Timeline.Controllers;
-using Timeline.Entities.Http;
+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;
@@ -28,56 +29,92 @@ namespace Timeline.Tests }
[Fact]
- public async void CreateTokenTest_UserNotExist()
+ public async void CreateToken_MissingUsername()
{
using (var client = _factory.CreateDefaultClient())
{
- var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "usernotexist", Password = "???" });
- Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
- var body = await response.ReadBodyAsJson<CommonResponse>();
- Assert.Equal(TokenController.ErrorCodes.Create_UserNotExist, body.Code);
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
+ new CreateTokenRequest { Username = null, Password = "user" });
+ }
+ }
+
+ [Fact]
+ public async void CreateToken_InvalidModel_MissingPassword()
+ {
+ using (var client = _factory.CreateDefaultClient())
+ {
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
+ new CreateTokenRequest { Username = "user", Password = null });
}
}
[Fact]
- public async void CreateTokenTest_BadPassword()
+ public async void CreateToken_InvalidModel_BadExpireOffset()
{
using (var client = _factory.CreateDefaultClient())
{
- var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "user", Password = "???" });
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
+ new CreateTokenRequest
+ {
+ Username = MockUsers.UserUsername,
+ Password = MockUsers.UserPassword,
+ ExpireOffset = -1000
+ });
+ }
+ }
+
+ [Fact]
+ public async void CreateToken_UserNotExist()
+ {
+ using (var client = _factory.CreateDefaultClient())
+ {
+ var response = await client.PostAsJsonAsync(CreateTokenUrl,
+ new CreateTokenRequest { Username = "usernotexist", Password = "???" });
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.ReadBodyAsJson<CommonResponse>();
- Assert.Equal(TokenController.ErrorCodes.Create_BadPassword, body.Code);
+ Assert.Equal(TokenController.ErrorCodes.Create_UserNotExist, body.Code);
}
}
[Fact]
- public async void CreateTokenTest_BadExpireOffset()
+ public async void CreateToken_BadPassword()
{
using (var client = _factory.CreateDefaultClient())
{
- var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "???", Password = "???", ExpireOffset = -1000 });
+ var response = await client.PostAsJsonAsync(CreateTokenUrl,
+ new CreateTokenRequest { Username = MockUsers.UserUsername, Password = "???" });
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.ReadBodyAsJson<CommonResponse>();
- Assert.Equal(TokenController.ErrorCodes.Create_BadExpireOffset, body.Code);
+ Assert.Equal(TokenController.ErrorCodes.Create_BadPassword, body.Code);
}
}
[Fact]
- public async void CreateTokenTest_Success()
+ public async void CreateToken_Success()
{
using (var client = _factory.CreateDefaultClient())
{
- var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "user", Password = "user" });
+ var response = await client.PostAsJsonAsync(CreateTokenUrl,
+ new CreateTokenRequest { Username = MockUsers.UserUsername, Password = MockUsers.UserPassword });
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.ReadBodyAsJson<CreateTokenResponse>();
Assert.NotEmpty(body.Token);
- Assert.Equal(TestMockUsers.MockUserInfos.Where(u => u.Username == "user").Single(), body.User, UserInfoComparers.EqualityComparer);
+ Assert.Equal(MockUsers.UserUserInfo, body.User, UserInfoComparers.EqualityComparer);
+ }
+ }
+
+ [Fact]
+ public async void VerifyToken_InvalidModel_MissingToken()
+ {
+ using (var client = _factory.CreateDefaultClient())
+ {
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, VerifyTokenUrl,
+ new VerifyTokenRequest { Token = null });
}
}
[Fact]
- public async void VerifyTokenTest_BadToken()
+ public async void VerifyToken_BadToken()
{
using (var client = _factory.CreateDefaultClient())
{
@@ -89,7 +126,7 @@ namespace Timeline.Tests }
[Fact]
- public async void VerifyTokenTest_BadVersion_AND_UserNotExist()
+ public async void VerifyToken_BadVersion_AND_UserNotExist()
{
using (var client = _factory.CreateDefaultClient())
{
@@ -131,7 +168,7 @@ namespace Timeline.Tests }
[Fact]
- public async void VerifyTokenTest_Expired()
+ public async void VerifyToken_Expired()
{
using (var client = _factory.CreateDefaultClient())
{
@@ -139,8 +176,9 @@ namespace Timeline.Tests // because verify logic is encapsuled in other library.
var mockClock = _factory.GetTestClock();
mockClock.MockCurrentTime = DateTime.Now - TimeSpan.FromDays(2);
- var token = (await client.CreateUserTokenAsync("user", "user", 1)).Token;
- var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
+ var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword, 1)).Token;
+ var response = await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = token });
var body = await response.ReadBodyAsJson<CommonResponse>();
Assert.Equal(TokenController.ErrorCodes.Verify_Expired, body.Code);
mockClock.MockCurrentTime = null;
@@ -148,15 +186,16 @@ namespace Timeline.Tests }
[Fact]
- public async void VerifyTokenTest_Success()
+ public async void VerifyToken_Success()
{
using (var client = _factory.CreateDefaultClient())
{
- var createTokenResult = await client.CreateUserTokenAsync("user", "user");
- var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = createTokenResult.Token });
+ var createTokenResult = await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword);
+ var response = await client.PostAsJsonAsync(VerifyTokenUrl,
+ new VerifyTokenRequest { Token = createTokenResult.Token });
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = JsonConvert.DeserializeObject<VerifyTokenResponse>(await response.Content.ReadAsStringAsync());
- Assert.Equal(TestMockUsers.MockUserInfos.Where(u => u.Username == "user").Single(), body.User, UserInfoComparers.EqualityComparer);
+ Assert.Equal(MockUsers.UserUserInfo, body.User, UserInfoComparers.EqualityComparer);
}
}
}
diff --git a/Timeline.Tests/UserUnitTest.cs b/Timeline.Tests/UserUnitTest.cs index de429c7f..c5c91d34 100644 --- a/Timeline.Tests/UserUnitTest.cs +++ b/Timeline.Tests/UserUnitTest.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc.Testing;
-using Newtonsoft.Json;
-using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading.Tasks;
-using Timeline.Entities;
+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;
@@ -21,15 +23,185 @@ namespace Timeline.Tests }
[Fact]
- public async Task UserTest()
+ public async Task Get_Users_List()
{
- using (var client = await _factory.CreateClientWithUser("admin", "admin"))
+ using (var client = await _factory.CreateClientAsAdmin())
{
- var res1 = await client.GetAsync("users");
- Assert.Equal(HttpStatusCode.OK, res1.StatusCode);
- var users = JsonConvert.DeserializeObject<UserInfo[]>(await res1.Content.ReadAsStringAsync()).ToList();
- users.Sort(UserInfoComparers.Comparer);
- Assert.Equal(TestMockUsers.MockUserInfos, users, UserInfoComparers.EqualityComparer);
+ var res = await client.GetAsync("users");
+ Assert.Equal(HttpStatusCode.OK, res.StatusCode);
+
+ // Because tests are running asyncronized. So database may be modified and
+ // we can't check the exact user lists at this point. So only check the format.
+
+ // var users = (await res.ReadBodyAsJson<UserInfo[]>()).ToList();
+ // users.Sort(UserInfoComparers.Comparer);
+ // Assert.Equal(MockUsers.UserInfos, users, UserInfoComparers.EqualityComparer);
+ await res.ReadBodyAsJson<UserInfo[]>();
+ }
+ }
+
+ [Fact]
+ public async Task Get_Users_User()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ var res = await client.GetAsync("users/" + MockUsers.UserUsername);
+ res.AssertOk();
+ var user = await res.ReadBodyAsJson<UserInfo>();
+ Assert.Equal(MockUsers.UserUserInfo, user, UserInfoComparers.EqualityComparer);
+ }
+ }
+
+ [Fact]
+ public async Task Get_Users_404()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ var res = await client.GetAsync("users/usernotexist");
+ res.AssertNotFound();
+ var body = await res.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(UserController.ErrorCodes.Get_NotExist, body.Code);
+ }
+ }
+
+ [Fact]
+ public async Task Put_Patch_Delete_User()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ const string username = "putpatchdeleteuser";
+ const string password = "password";
+ const string url = "users/" + username;
+
+ // Put Invalid Model
+ await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = null, Administrator = false });
+ await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = password, Administrator = null });
+
+ async Task CheckAdministrator(bool administrator)
+ {
+ var res = await client.GetAsync(url);
+ res.AssertOk();
+ var body = await res.ReadBodyAsJson<UserInfo>();
+ Assert.Equal(administrator, body.Administrator);
+ }
+
+ {
+ // Put Created.
+ var res = await client.PutAsJsonAsync(url, new UserPutRequest
+ {
+ Password = password,
+ Administrator = false
+ });
+ await res.AssertIsPutCreated();
+ await CheckAdministrator(false);
+ }
+
+ {
+ // Put Modified.
+ var res = await client.PutAsJsonAsync(url, new UserPutRequest
+ {
+ Password = password,
+ Administrator = true
+ });
+ await res.AssertIsPutModified();
+ await CheckAdministrator(true);
+ }
+
+ // Patch Not Exist
+ {
+ var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
+ res.AssertNotFound();
+ var body = await res.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(UserController.ErrorCodes.Patch_NotExist, body.Code);
+ }
+
+ // Patch Success
+ {
+ var res = await client.PatchAsJsonAsync(url, new UserPatchRequest { Administrator = false });
+ res.AssertOk();
+ await CheckAdministrator(false);
+ }
+
+ // Delete Deleted
+ {
+ var res = await client.DeleteAsync(url);
+ await res.AssertIsDeleteDeleted();
+
+ var res2 = await client.GetAsync(url);
+ res2.AssertNotFound();
+ }
+
+ // Delete Not Exist
+ {
+ var res = await client.DeleteAsync(url);
+ await res.AssertIsDeleteNotExist();
+ }
+ }
+ }
+
+
+ public class ChangePasswordUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>
+ {
+ private const string url = "userop/changepassword";
+
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public ChangePasswordUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
+ {
+ _factory = factory.WithTestLogging(outputHelper);
+ }
+
+
+ [Fact]
+ public async Task InvalidModel_OldPassword()
+ {
+ using (var client = await _factory.CreateClientAsUser())
+ {
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, url, new ChangePasswordRequest { OldPassword = null, NewPassword = "???" });
+ }
+ }
+
+ [Fact]
+ public async Task InvalidModel_NewPassword()
+ {
+ using (var client = await _factory.CreateClientAsUser())
+ {
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, url, new ChangePasswordRequest { OldPassword = "???", NewPassword = null });
+ }
+ }
+
+ [Fact]
+ public async Task BadOldPassword()
+ {
+ using (var client = await _factory.CreateClientAsUser())
+ {
+ var res = await client.PostAsJsonAsync(url, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" });
+ res.AssertBadRequest();
+ var body = await res.ReadBodyAsJson<CommonResponse>();
+ Assert.Equal(UserController.ErrorCodes.ChangePassword_BadOldPassword, body.Code);
+ }
+ }
+
+ [Fact]
+ public async Task Success()
+ {
+ const string username = "changepasswordtest";
+ const string password = "password";
+
+ // create a new user to avoid interference
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ var res = await client.PutAsJsonAsync("users/" + username, new UserPutRequest { Password = password, Administrator = false });
+ Assert.Equal(HttpStatusCode.Created, res.StatusCode);
+ }
+
+ using (var client = await _factory.CreateClientWithCredential(username, password))
+ {
+ const string newPassword = "new";
+ var res = await client.PostAsJsonAsync(url, new ChangePasswordRequest { OldPassword = password, NewPassword = newPassword });
+ res.AssertOk();
+ await client.CreateUserTokenAsync(username, newPassword);
+ }
}
}
}
diff --git a/Timeline.Tests/coverletArgs.runsettings b/Timeline.Tests/coverletArgs.runsettings new file mode 100644 index 00000000..24cd1822 --- /dev/null +++ b/Timeline.Tests/coverletArgs.runsettings @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<RunSettings>
+ <DataCollectionRunSettings>
+ <DataCollectors>
+ <DataCollector friendlyName="XPlat code coverage">
+ <Configuration>
+ <!-- [Assembly-Filter]Type-Filter -->
+ <Exclude>[xunit.*]*,[Timeline]Timeline.Migrations.*</Exclude>
+ </Configuration>
+ </DataCollector>
+ </DataCollectors>
+ </DataCollectionRunSettings>
+</RunSettings>
diff --git a/Timeline/Authenticate/Attribute.cs b/Timeline/Authenticate/Attribute.cs index 1875a21b..239a2a1c 100644 --- a/Timeline/Authenticate/Attribute.cs +++ b/Timeline/Authenticate/Attribute.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Authorization;
-using Timeline.Models;
+using Timeline.Entities;
namespace Timeline.Authenticate
{
diff --git a/Timeline/Authenticate/AuthHandler.cs b/Timeline/Authenticate/AuthHandler.cs index 5b26716d..f9409c1a 100644 --- a/Timeline/Authenticate/AuthHandler.cs +++ b/Timeline/Authenticate/AuthHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
+using Timeline.Models;
using Timeline.Services;
namespace Timeline.Authenticate
@@ -80,7 +81,7 @@ namespace Timeline.Authenticate var identity = new ClaimsIdentity(AuthConstants.Scheme);
identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String));
- identity.AddClaims(Entities.UserUtility.IsAdminToRoleArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
+ identity.AddClaims(UserUtility.IsAdminToRoleArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
principal.AddIdentity(identity);
diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index ff397518..3c166448 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -5,13 +5,14 @@ using Microsoft.IdentityModel.Tokens; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using Timeline.Entities.Http;
+using Timeline.Models.Http;
using Timeline.Services;
using static Timeline.Helpers.MyLogHelper;
namespace Timeline.Controllers
{
[Route("token")]
+ [ApiController]
public class TokenController : Controller
{
private static class LoggingEventIds
@@ -60,22 +61,9 @@ namespace Timeline.Controllers Pair("Expire Offset (in days)", request.ExpireOffset)));
}
- TimeSpan? expireOffset = null;
- if (request.ExpireOffset != null)
- {
- if (request.ExpireOffset.Value <= 0.0)
- {
- const string message = "Expire time is not bigger than 0.";
- var code = ErrorCodes.Create_BadExpireOffset;
- LogFailure(message, code);
- return BadRequest(new CommonResponse(code, message));
- }
- expireOffset = TimeSpan.FromDays(request.ExpireOffset.Value);
- }
-
try
{
- var expiredTime = expireOffset == null ? null : (DateTime?)(_clock.GetCurrentTime() + expireOffset.Value);
+ 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),
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index 8d338949..6f2fe77f 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -4,24 +4,23 @@ using Microsoft.Extensions.Logging; using System;
using System.Threading.Tasks;
using Timeline.Authenticate;
-using Timeline.Entities;
-using Timeline.Entities.Http;
+using Timeline.Models;
+using Timeline.Models.Http;
using Timeline.Services;
using static Timeline.Helpers.MyLogHelper;
namespace Timeline.Controllers
{
+ [ApiController]
public class UserController : Controller
{
- private static class ErrorCodes
+ public static class ErrorCodes
{
- public const int Get_NotExists = -1001;
+ public const int Get_NotExist = -1001;
- public const int Put_NoPassword = -2001;
+ public const int Patch_NotExist = -2001;
- public const int Patch_NotExists = -3001;
-
- public const int ChangePassword_BadOldPassword = -4001;
+ public const int ChangePassword_BadOldPassword = -3001;
}
private readonly ILogger<UserController> _logger;
@@ -39,28 +38,22 @@ namespace Timeline.Controllers return Ok(await _userService.ListUsers());
}
- [HttpGet("user/{username}"), AdminAuthorize]
+ [HttpGet("users/{username}"), AdminAuthorize]
public async Task<IActionResult> Get([FromRoute] string username)
{
var user = await _userService.GetUser(username);
if (user == null)
{
_logger.LogInformation(FormatLogMessage("Attempt to get a non-existent user.", Pair("Username", username)));
- return NotFound(new CommonResponse(ErrorCodes.Get_NotExists, "The user does not exist."));
+ return NotFound(new CommonResponse(ErrorCodes.Get_NotExist, "The user does not exist."));
}
return Ok(user);
}
- [HttpPut("user/{username}"), AdminAuthorize]
+ [HttpPut("users/{username}"), AdminAuthorize]
public async Task<IActionResult> Put([FromBody] UserPutRequest request, [FromRoute] string username)
{
- if (request.Password == null) // This place will be refactored.
- {
- _logger.LogInformation("Attempt to put a user without a password. Username: {} .", username);
- return BadRequest();
- }
-
- var result = await _userService.PutUser(username, request.Password, request.Administrator);
+ var result = await _userService.PutUser(username, request.Password, request.Administrator.Value);
switch (result)
{
case PutResult.Created:
@@ -74,7 +67,7 @@ namespace Timeline.Controllers }
}
- [HttpPatch("user/{username}"), AdminAuthorize]
+ [HttpPatch("users/{username}"), AdminAuthorize]
public async Task<IActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute] string username)
{
try
@@ -85,11 +78,11 @@ namespace Timeline.Controllers catch (UserNotExistException e)
{
_logger.LogInformation(e, FormatLogMessage("Attempt to patch a non-existent user.", Pair("Username", username)));
- return BadRequest(new CommonResponse(ErrorCodes.Patch_NotExists, "The user does not exist."));
+ return NotFound(new CommonResponse(ErrorCodes.Patch_NotExist, "The user does not exist."));
}
}
- [HttpDelete("user/{username}"), AdminAuthorize]
+ [HttpDelete("users/{username}"), AdminAuthorize]
public async Task<IActionResult> Delete([FromRoute] string username)
{
try
diff --git a/Timeline/Controllers/UserTestController.cs b/Timeline/Controllers/UserTestController.cs index f65d9857..2a5f36a1 100644 --- a/Timeline/Controllers/UserTestController.cs +++ b/Timeline/Controllers/UserTestController.cs @@ -5,6 +5,7 @@ using Timeline.Authenticate; namespace Timeline.Controllers
{
[Route("Test/User")]
+ [ApiController]
public class UserTestController : Controller
{
[HttpGet("[action]")]
diff --git a/Timeline/Models/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs index 2e33bf8d..c5a84342 100644 --- a/Timeline/Models/DatabaseContext.cs +++ b/Timeline/Entities/DatabaseContext.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-namespace Timeline.Models
+namespace Timeline.Entities
{
public static class UserRoles
{
diff --git a/Timeline/Helpers/InvalidModelResponseFactory.cs b/Timeline/Helpers/InvalidModelResponseFactory.cs new file mode 100644 index 00000000..c792e845 --- /dev/null +++ b/Timeline/Helpers/InvalidModelResponseFactory.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc;
+using System.Text;
+using Timeline.Models.Http;
+
+namespace Timeline.Helpers
+{
+ public static class InvalidModelResponseFactory
+ {
+ public static IActionResult Factory(ActionContext context)
+ {
+ var modelState = context.ModelState;
+
+ var messageBuilder = new StringBuilder();
+ foreach (var model in modelState)
+ foreach (var error in model.Value.Errors)
+ {
+ messageBuilder.Append(model.Key);
+ messageBuilder.Append(" : ");
+ messageBuilder.AppendLine(error.ErrorMessage);
+ }
+
+ return new BadRequestObjectResult(CommonResponse.InvalidModel(messageBuilder.ToString()));
+ }
+ }
+}
diff --git a/Timeline/Migrations/20190412102517_InitCreate.Designer.cs b/Timeline/Migrations/20190412102517_InitCreate.Designer.cs index 5281da05..86a46720 100644 --- a/Timeline/Migrations/20190412102517_InitCreate.Designer.cs +++ b/Timeline/Migrations/20190412102517_InitCreate.Designer.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Timeline.Models;
+using Timeline.Entities;
namespace Timeline.Migrations
{
diff --git a/Timeline/Migrations/20190412144150_AddAdminUser.Designer.cs b/Timeline/Migrations/20190412144150_AddAdminUser.Designer.cs index b3139efa..2e6b5f74 100644 --- a/Timeline/Migrations/20190412144150_AddAdminUser.Designer.cs +++ b/Timeline/Migrations/20190412144150_AddAdminUser.Designer.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Timeline.Models;
+using Timeline.Entities;
namespace Timeline.Migrations
{
diff --git a/Timeline/Migrations/20190412153003_MakeColumnsInUserNotNull.Designer.cs b/Timeline/Migrations/20190412153003_MakeColumnsInUserNotNull.Designer.cs index e2adf9d4..263efb8a 100644 --- a/Timeline/Migrations/20190412153003_MakeColumnsInUserNotNull.Designer.cs +++ b/Timeline/Migrations/20190412153003_MakeColumnsInUserNotNull.Designer.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Timeline.Models;
+using Timeline.Entities;
namespace Timeline.Migrations
{
diff --git a/Timeline/Migrations/20190719115321_Add-User-Version.Designer.cs b/Timeline/Migrations/20190719115321_Add-User-Version.Designer.cs index 0216ff5b..d61259b5 100644 --- a/Timeline/Migrations/20190719115321_Add-User-Version.Designer.cs +++ b/Timeline/Migrations/20190719115321_Add-User-Version.Designer.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Timeline.Models;
+using Timeline.Entities;
namespace Timeline.Migrations
{
diff --git a/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/Timeline/Migrations/DatabaseContextModelSnapshot.cs index c26ae5e6..48afb2c8 100644 --- a/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Timeline.Models;
+using Timeline.Entities;
namespace Timeline.Migrations
{
diff --git a/Timeline/Entities/Http/Common.cs b/Timeline/Models/Http/Common.cs index 4cdc85dd..b4932754 100644 --- a/Timeline/Entities/Http/Common.cs +++ b/Timeline/Models/Http/Common.cs @@ -1,7 +1,21 @@ -namespace Timeline.Entities.Http
+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 static CommonResponse InvalidModel(string message)
+ {
+ return new CommonResponse(ErrorCodes.InvalidModel, message);
+ }
+
public CommonResponse()
{
diff --git a/Timeline/Entities/Http/Token.cs b/Timeline/Models/Http/Token.cs index 5e261f30..68a66d0a 100644 --- a/Timeline/Entities/Http/Token.cs +++ b/Timeline/Models/Http/Token.cs @@ -1,11 +1,16 @@ -namespace Timeline.Entities.Http
+using System.ComponentModel.DataAnnotations;
+
+namespace Timeline.Models.Http
{
public class CreateTokenRequest
{
+ [Required]
public string Username { get; set; }
+ [Required]
public string Password { get; set; }
- // in day
- public double? ExpireOffset { get; set; }
+ // in days, optional
+ [Range(1, 365)]
+ public int? ExpireOffset { get; set; }
}
public class CreateTokenResponse
@@ -16,6 +21,7 @@ namespace Timeline.Entities.Http public class VerifyTokenRequest
{
+ [Required]
public string Token { get; set; }
}
diff --git a/Timeline/Entities/Http/User.cs b/Timeline/Models/Http/User.cs index e9aaf795..d45543fb 100644 --- a/Timeline/Entities/Http/User.cs +++ b/Timeline/Models/Http/User.cs @@ -1,9 +1,13 @@ -namespace Timeline.Entities.Http
+using System.ComponentModel.DataAnnotations;
+
+namespace Timeline.Models.Http
{
public class UserPutRequest
{
+ [Required]
public string Password { get; set; }
- public bool Administrator { get; set; }
+ [Required]
+ public bool? Administrator { get; set; }
}
public class UserPatchRequest
@@ -14,7 +18,9 @@ namespace Timeline.Entities.Http public class ChangePasswordRequest
{
+ [Required]
public string OldPassword { get; set; }
+ [Required]
public string NewPassword { get; set; }
}
}
diff --git a/Timeline/Entities/PutResult.cs b/Timeline/Models/PutResult.cs index 32b87e25..544602eb 100644 --- a/Timeline/Entities/PutResult.cs +++ b/Timeline/Models/PutResult.cs @@ -1,4 +1,4 @@ -namespace Timeline.Entities
+namespace Timeline.Models
{
/// <summary>
/// Represents the result of a "put" operation.
diff --git a/Timeline/Entities/UserInfo.cs b/Timeline/Models/UserInfo.cs index c75168b8..e502855b 100644 --- a/Timeline/Entities/UserInfo.cs +++ b/Timeline/Models/UserInfo.cs @@ -1,4 +1,4 @@ -namespace Timeline.Entities
+namespace Timeline.Models
{
public sealed class UserInfo
{
diff --git a/Timeline/Entities/UserUtility.cs b/Timeline/Models/UserUtility.cs index 351d0299..405987b5 100644 --- a/Timeline/Entities/UserUtility.cs +++ b/Timeline/Models/UserUtility.cs @@ -1,9 +1,9 @@ using System;
using System.Linq;
-using Timeline.Models;
+using Timeline.Entities;
using Timeline.Services;
-namespace Timeline.Entities
+namespace Timeline.Models
{
public static class UserUtility
{
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 7fe7a2b6..28218612 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -6,8 +6,8 @@ using System.Linq; using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Models;
-using static Timeline.Entities.UserUtility;
using static Timeline.Helpers.MyLogHelper;
+using static Timeline.Models.UserUtility;
namespace Timeline.Services
{
diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index 0ce6b989..414bc705 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -8,7 +8,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection;
using Timeline.Authenticate;
using Timeline.Configs;
-using Timeline.Models;
+using Timeline.Entities;
+using Timeline.Helpers;
using Timeline.Services;
namespace Timeline
@@ -29,7 +30,11 @@ namespace Timeline // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
+ services.AddMvc()
+ .ConfigureApiBehaviorOptions(options =>{
+ options.InvalidModelStateResponseFactory = InvalidModelResponseFactory.Factory;
+ })
+ .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddCors(options =>
{
@@ -84,7 +89,7 @@ namespace Timeline app.UseAuthentication();
- app.UseMvcWithDefaultRoute();
+ app.UseMvc();
}
}
}
|