aboutsummaryrefslogtreecommitdiff
path: root/Timeline.Tests
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline.Tests')
-rw-r--r--Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs388
-rw-r--r--Timeline.Tests/Controllers/TokenControllerTest.cs3
-rw-r--r--Timeline.Tests/Controllers/UserControllerTest.cs1
-rw-r--r--Timeline.Tests/DatabaseTest.cs4
-rw-r--r--Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs40
-rw-r--r--Timeline.Tests/Helpers/MockUser.cs24
-rw-r--r--Timeline.Tests/Helpers/PrincipalHelper.cs23
-rw-r--r--Timeline.Tests/Helpers/ResponseAssertions.cs2
-rw-r--r--Timeline.Tests/Helpers/TestApplication.cs24
-rw-r--r--Timeline.Tests/Helpers/TestClock.cs (renamed from Timeline.Tests/Mock/Services/TestClock.cs)30
-rw-r--r--Timeline.Tests/Helpers/TestDatabase.cs89
-rw-r--r--Timeline.Tests/Helpers/UseCultureAttribute.cs143
-rw-r--r--Timeline.Tests/IntegratedTests/AuthorizationTest.cs23
-rw-r--r--Timeline.Tests/IntegratedTests/I18nTest.cs16
-rw-r--r--Timeline.Tests/IntegratedTests/IntegratedTestBase.cs94
-rw-r--r--Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs495
-rw-r--r--Timeline.Tests/IntegratedTests/TokenTest.cs46
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTest.cs21
-rw-r--r--Timeline.Tests/IntegratedTests/UserDetailTest.cs25
-rw-r--r--Timeline.Tests/IntegratedTests/UserTest.cs68
-rw-r--r--Timeline.Tests/Mock/Data/TestDatabase.cs42
-rw-r--r--Timeline.Tests/Mock/Data/TestUsers.cs50
-rw-r--r--Timeline.Tests/Services/UserAvatarServiceTest.cs9
-rw-r--r--Timeline.Tests/Services/UserDetailServiceTest.cs7
-rw-r--r--Timeline.Tests/Timeline.Tests.csproj8
-rw-r--r--Timeline.Tests/UsernameValidatorUnitTest.cs1
26 files changed, 1296 insertions, 380 deletions
diff --git a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs
new file mode 100644
index 00000000..372ba8a7
--- /dev/null
+++ b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs
@@ -0,0 +1,388 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading.Tasks;
+using Timeline.Controllers;
+using Timeline.Filters;
+using Timeline.Models;
+using Timeline.Models.Http;
+using Timeline.Models.Validation;
+using Timeline.Services;
+using Timeline.Tests.Helpers;
+using Xunit;
+
+namespace Timeline.Tests.Controllers
+{
+ public class PersonalTimelineControllerTest : IDisposable
+ {
+ private readonly Mock<IPersonalTimelineService> _service;
+
+ private readonly PersonalTimelineController _controller;
+
+ public PersonalTimelineControllerTest()
+ {
+ _service = new Mock<IPersonalTimelineService>();
+ _controller = new PersonalTimelineController(NullLogger<PersonalTimelineController>.Instance, _service.Object);
+ }
+
+ public void Dispose()
+ {
+ _controller.Dispose();
+ }
+
+ [Fact]
+ public void AttributeTest()
+ {
+ static void AssertUsernameParameter(MethodInfo m)
+ {
+ m.GetParameter("username")
+ .Should().BeDecoratedWith<FromRouteAttribute>()
+ .And.BeDecoratedWith<UsernameAttribute>();
+ }
+
+ static void AssertBodyParamter<TBody>(MethodInfo m)
+ {
+ var p = m.GetParameter("body");
+ p.Should().BeDecoratedWith<FromBodyAttribute>();
+ p.ParameterType.Should().Be(typeof(TBody));
+ }
+
+ var type = typeof(PersonalTimelineController);
+ type.Should().BeDecoratedWith<ApiControllerAttribute>();
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelineGet));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<HttpGetAttribute>();
+ AssertUsernameParameter(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.PostListGet));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<HttpGetAttribute>();
+ AssertUsernameParameter(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.PostOperationCreate));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelinePostCreateRequest>(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.PostOperationDelete));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelinePostDeleteRequest>(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeProperty));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelinePropertyChangeRequest>(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeMember));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelineMemberChangeRequest>(m);
+ }
+ }
+
+ const string authUsername = "authuser";
+ private void SetUser(bool administrator)
+ {
+ _controller.ControllerContext = new ControllerContext
+ {
+ HttpContext = new DefaultHttpContext
+ {
+ User = PrincipalHelper.Create(authUsername, administrator)
+ }
+ };
+ }
+
+ [Fact]
+ public async Task TimelineGet()
+ {
+ const string username = "username";
+ var timelineInfo = new BaseTimelineInfo();
+ _service.Setup(s => s.GetTimeline(username)).ReturnsAsync(timelineInfo);
+ (await _controller.TimelineGet(username)).Value.Should().Be(timelineInfo);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostListGet_Forbid()
+ {
+ const string username = "username";
+ SetUser(false);
+ _service.Setup(s => s.HasReadPermission(username, authUsername)).ReturnsAsync(false);
+ var result = (await _controller.PostListGet(username)).Result
+ .Should().BeAssignableTo<ObjectResult>()
+ .Which;
+ result.StatusCode.Should().Be(StatusCodes.Status403Forbidden);
+ result.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostListGetForbid);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostListGet_Admin_Success()
+ {
+ const string username = "username";
+ SetUser(true);
+ _service.Setup(s => s.GetPosts(username)).ReturnsAsync(new List<TimelinePostInfo>());
+ (await _controller.PostListGet(username)).Value
+ .Should().BeAssignableTo<IList<TimelinePostInfo>>()
+ .Which.Should().NotBeNull().And.BeEmpty();
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostListGet_User_Success()
+ {
+ const string username = "username";
+ SetUser(false);
+ _service.Setup(s => s.HasReadPermission(username, authUsername)).ReturnsAsync(true);
+ _service.Setup(s => s.GetPosts(username)).ReturnsAsync(new List<TimelinePostInfo>());
+ (await _controller.PostListGet(username)).Value
+ .Should().BeAssignableTo<IList<TimelinePostInfo>>()
+ .Which.Should().NotBeNull().And.BeEmpty();
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationCreate_Forbid()
+ {
+ const string username = "username";
+ const string content = "cccc";
+ SetUser(false);
+ _service.Setup(s => s.IsMemberOf(username, authUsername)).ReturnsAsync(false);
+ var result = (await _controller.PostOperationCreate(username, new TimelinePostCreateRequest
+ {
+ Content = content,
+ Time = null
+ })).Result.Should().NotBeNull().And.BeAssignableTo<ObjectResult>().Which;
+ result.StatusCode.Should().Be(StatusCodes.Status403Forbidden);
+ result.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationCreateForbid);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationCreate_Admin_Success()
+ {
+ const string username = "username";
+ const string content = "cccc";
+ var response = new TimelinePostCreateResponse
+ {
+ Id = 3,
+ Time = DateTime.Now
+ };
+ SetUser(true);
+ _service.Setup(s => s.CreatePost(username, authUsername, content, null)).ReturnsAsync(response);
+ var resultValue = (await _controller.PostOperationCreate(username, new TimelinePostCreateRequest
+ {
+ Content = content,
+ Time = null
+ })).Value;
+ resultValue.Should().NotBeNull()
+ .And.BeAssignableTo<TimelinePostCreateResponse>()
+ .And.BeEquivalentTo(response);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationCreate_User_Success()
+ {
+ const string username = "username";
+ const string content = "cccc";
+ var response = new TimelinePostCreateResponse
+ {
+ Id = 3,
+ Time = DateTime.Now
+ };
+ SetUser(false);
+ _service.Setup(s => s.IsMemberOf(username, authUsername)).ReturnsAsync(true);
+ _service.Setup(s => s.CreatePost(username, authUsername, content, null)).ReturnsAsync(response);
+ var resultValue = (await _controller.PostOperationCreate(username, new TimelinePostCreateRequest
+ {
+ Content = content,
+ Time = null
+ })).Value;
+ resultValue.Should().NotBeNull()
+ .And.BeAssignableTo<TimelinePostCreateResponse>()
+ .And.BeEquivalentTo(response);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationDelete_Forbid()
+ {
+ const string username = "username";
+ const long postId = 2;
+ SetUser(false);
+ _service.Setup(s => s.HasPostModifyPermission(username, postId, authUsername)).ReturnsAsync(false);
+ var result = (await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest
+ {
+ Id = postId
+ })).Should().NotBeNull().And.BeAssignableTo<ObjectResult>().Which;
+ result.StatusCode.Should().Be(StatusCodes.Status403Forbidden);
+ result.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationDeleteForbid);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationDelete_NotExist()
+ {
+ const string username = "username";
+ const long postId = 2;
+ SetUser(true);
+ _service.Setup(s => s.DeletePost(username, postId)).ThrowsAsync(new TimelinePostNotExistException());
+ var result = (await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest
+ {
+ Id = postId
+ })).Should().NotBeNull().And.BeAssignableTo<ObjectResult>().Which;
+ result.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
+ result.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationDeleteNotExist);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationDelete_Admin_Success()
+ {
+ const string username = "username";
+ const long postId = 2;
+ SetUser(true);
+ _service.Setup(s => s.DeletePost(username, postId)).Returns(Task.CompletedTask);
+ var result = await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest
+ {
+ Id = postId
+ });
+ result.Should().NotBeNull().And.BeAssignableTo<OkResult>();
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PostOperationDelete_User_Success()
+ {
+ const string username = "username";
+ const long postId = 2;
+ SetUser(false);
+ _service.Setup(s => s.DeletePost(username, postId)).Returns(Task.CompletedTask);
+ _service.Setup(s => s.HasPostModifyPermission(username, postId, authUsername)).ReturnsAsync(true);
+ var result = await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest
+ {
+ Id = postId
+ });
+ result.Should().NotBeNull().And.BeAssignableTo<OkResult>();
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task TimelineChangeProperty_Success()
+ {
+ const string username = "username";
+ var req = new TimelinePropertyChangeRequest
+ {
+ Description = "",
+ Visibility = TimelineVisibility.Private
+ };
+ _service.Setup(s => s.ChangeProperty(username, req)).Returns(Task.CompletedTask);
+ var result = await _controller.TimelineChangeProperty(username, req);
+ result.Should().NotBeNull().And.BeAssignableTo<OkResult>();
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task TimelineChangeMember_Success()
+ {
+ const string username = "username";
+ var add = new List<string> { "aaa" };
+ var remove = new List<string> { "rrr" };
+ _service.Setup(s => s.ChangeMember(username, add, remove)).Returns(Task.CompletedTask);
+ var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest
+ {
+ Add = add,
+ Remove = remove
+ });
+ result.Should().NotBeNull().And.BeAssignableTo<OkResult>();
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task TimelineChangeMember_UsernameBadFormat()
+ {
+ const string username = "username";
+ var add = new List<string> { "aaa" };
+ var remove = new List<string> { "rrr" };
+ _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync(
+ new TimelineMemberOperationUserException("test", new UsernameBadFormatException()));
+ var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest
+ {
+ Add = add,
+ Remove = remove
+ });
+ result.Should().NotBeNull().And.BeAssignableTo<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(ErrorCodes.Http.Common.InvalidModel);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task TimelineChangeMember_AddNotExist()
+ {
+ const string username = "username";
+ var add = new List<string> { "aaa" };
+ var remove = new List<string> { "rrr" };
+ _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync(
+ new TimelineMemberOperationUserException("test", new UserNotExistException()));
+ var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest
+ {
+ Add = add,
+ Remove = remove
+ });
+ result.Should().NotBeNull().And.BeAssignableTo<BadRequestObjectResult>()
+ .Which.Value.Should().BeAssignableTo<CommonResponse>()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.ChangeMemberUserNotExist);
+ _service.VerifyAll();
+ }
+
+ [Fact]
+ public async Task TimelineChangeMember_UnknownTimelineMemberOperationUserException()
+ {
+ const string username = "username";
+ var add = new List<string> { "aaa" };
+ var remove = new List<string> { "rrr" };
+ _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync(
+ new TimelineMemberOperationUserException("test", null));
+ await _controller.Awaiting(c => c.TimelineChangeMember(username, new TimelineMemberChangeRequest
+ {
+ Add = add,
+ Remove = remove
+ })).Should().ThrowAsync<TimelineMemberOperationUserException>(); // Should rethrow.
+ }
+ }
+}
diff --git a/Timeline.Tests/Controllers/TokenControllerTest.cs b/Timeline.Tests/Controllers/TokenControllerTest.cs
index 4a08ca0f..238fc237 100644
--- a/Timeline.Tests/Controllers/TokenControllerTest.cs
+++ b/Timeline.Tests/Controllers/TokenControllerTest.cs
@@ -8,8 +8,7 @@ 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 Timeline.Tests.Helpers;
using Xunit;
using static Timeline.ErrorCodes.Http.Token;
diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs
index 83b8cdcf..a5ca7a2b 100644
--- a/Timeline.Tests/Controllers/UserControllerTest.cs
+++ b/Timeline.Tests/Controllers/UserControllerTest.cs
@@ -12,7 +12,6 @@ using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Mock.Data;
using Xunit;
using static Timeline.ErrorCodes.Http.User;
diff --git a/Timeline.Tests/DatabaseTest.cs b/Timeline.Tests/DatabaseTest.cs
index fc153c24..a7b97c16 100644
--- a/Timeline.Tests/DatabaseTest.cs
+++ b/Timeline.Tests/DatabaseTest.cs
@@ -2,7 +2,7 @@
using System;
using System.Linq;
using Timeline.Entities;
-using Timeline.Tests.Mock.Data;
+using Timeline.Tests.Helpers;
using Xunit;
namespace Timeline.Tests
@@ -15,7 +15,7 @@ namespace Timeline.Tests
public DatabaseTest()
{
_database = new TestDatabase();
- _context = _database.DatabaseContext;
+ _context = _database.Context;
}
public void Dispose()
diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs
deleted file mode 100644
index 34d7e460..00000000
--- a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Microsoft.AspNetCore.Mvc.Testing;
-using Newtonsoft.Json;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Timeline.Models.Http;
-using Timeline.Tests.Mock.Data;
-
-namespace Timeline.Tests.Helpers.Authentication
-{
- public static class AuthenticationExtensions
- {
- private const string CreateTokenUrl = "/token/create";
-
- 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, Expire = expireOffset });
- response.Should().HaveStatusCode(200);
- var result = JsonConvert.DeserializeObject<CreateTokenResponse>(await response.Content.ReadAsStringAsync());
- return result;
- }
-
- 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(MockUser.User.Username, MockUser.User.Password);
- }
-
- public static Task<HttpClient> CreateClientAsAdmin<T>(this WebApplicationFactory<T> factory) where T : class
- {
- return factory.CreateClientWithCredential(MockUser.Admin.Username, MockUser.Admin.Password);
- }
- }
-}
diff --git a/Timeline.Tests/Helpers/MockUser.cs b/Timeline.Tests/Helpers/MockUser.cs
new file mode 100644
index 00000000..8d738525
--- /dev/null
+++ b/Timeline.Tests/Helpers/MockUser.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using Timeline.Models;
+
+namespace Timeline.Tests.Helpers
+{
+ public class MockUser
+ {
+ public MockUser(string username, string password, bool administrator)
+ {
+ Info = new UserInfo(username, administrator);
+ Password = password;
+ }
+
+ public UserInfo Info { get; set; }
+ public string Username => Info.Username;
+ public string Password { get; set; }
+ public bool Administrator => Info.Administrator;
+
+ public static MockUser User { get; } = new MockUser("user", "userpassword", false);
+ public static MockUser Admin { get; } = new MockUser("admin", "adminpassword", true);
+
+ public static IReadOnlyList<UserInfo> UserInfoList { get; } = new List<UserInfo> { User.Info, Admin.Info };
+ }
+}
diff --git a/Timeline.Tests/Helpers/PrincipalHelper.cs b/Timeline.Tests/Helpers/PrincipalHelper.cs
new file mode 100644
index 00000000..89f3f7b1
--- /dev/null
+++ b/Timeline.Tests/Helpers/PrincipalHelper.cs
@@ -0,0 +1,23 @@
+using System.Linq;
+using System.Security.Claims;
+using Timeline.Models;
+
+namespace Timeline.Tests.Helpers
+{
+ public static class PrincipalHelper
+ {
+ internal const string AuthScheme = "TESTAUTH";
+
+ internal static ClaimsPrincipal Create(string username, bool administrator)
+ {
+ var identity = new ClaimsIdentity(AuthScheme);
+ identity.AddClaim(new Claim(identity.NameClaimType, username, ClaimValueTypes.String));
+ identity.AddClaims(UserRoleConvert.ToArray(administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
+
+ var principal = new ClaimsPrincipal();
+ principal.AddIdentity(identity);
+
+ return principal;
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/ResponseAssertions.cs b/Timeline.Tests/Helpers/ResponseAssertions.cs
index 0e6f215b..6d764c68 100644
--- a/Timeline.Tests/Helpers/ResponseAssertions.cs
+++ b/Timeline.Tests/Helpers/ResponseAssertions.cs
@@ -88,7 +88,7 @@ namespace Timeline.Tests.Helpers
return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, null);
}
- var result = JsonConvert.DeserializeObject<T>(body);
+ var result = JsonConvert.DeserializeObject<T>(body); // TODO! catch and throw on bad format
return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, result);
}
}
diff --git a/Timeline.Tests/Helpers/TestApplication.cs b/Timeline.Tests/Helpers/TestApplication.cs
index b0187a30..a624da6b 100644
--- a/Timeline.Tests/Helpers/TestApplication.cs
+++ b/Timeline.Tests/Helpers/TestApplication.cs
@@ -1,35 +1,18 @@
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 TestDatabase Database { get; } = new TestDatabase();
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 =>
@@ -37,7 +20,7 @@ namespace Timeline.Tests.Helpers
services.AddEntityFrameworkSqlite();
services.AddDbContext<DatabaseContext>(options =>
{
- options.UseSqlite(DatabaseConnection);
+ options.UseSqlite(Database.Connection);
});
});
});
@@ -45,8 +28,7 @@ namespace Timeline.Tests.Helpers
public void Dispose()
{
- DatabaseConnection.Close();
- DatabaseConnection.Dispose();
+ Database.Dispose();
}
}
}
diff --git a/Timeline.Tests/Mock/Services/TestClock.cs b/Timeline.Tests/Helpers/TestClock.cs
index 6671395a..12b320d3 100644
--- a/Timeline.Tests/Mock/Services/TestClock.cs
+++ b/Timeline.Tests/Helpers/TestClock.cs
@@ -1,15 +1,15 @@
-using System;
-using Timeline.Services;
-
-namespace Timeline.Tests.Mock.Services
-{
- public class TestClock : IClock
- {
- public DateTime? MockCurrentTime { get; set; } = null;
-
- public DateTime GetCurrentTime()
- {
- return MockCurrentTime.GetValueOrDefault(DateTime.Now);
- }
- }
-}
+using System;
+using Timeline.Services;
+
+namespace Timeline.Tests.Helpers
+{
+ public class TestClock : IClock
+ {
+ public DateTime? MockCurrentTime { get; set; } = null;
+
+ public DateTime GetCurrentTime()
+ {
+ return MockCurrentTime.GetValueOrDefault(DateTime.Now);
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/TestDatabase.cs b/Timeline.Tests/Helpers/TestDatabase.cs
new file mode 100644
index 00000000..10224c27
--- /dev/null
+++ b/Timeline.Tests/Helpers/TestDatabase.cs
@@ -0,0 +1,89 @@
+using Microsoft.Data.Sqlite;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Timeline.Entities;
+using Timeline.Models;
+using Timeline.Services;
+
+namespace Timeline.Tests.Helpers
+{
+ public class TestDatabase : IDisposable
+ {
+ // currently password service is thread safe, so we share a static one.
+ private static PasswordService PasswordService { get; } = new PasswordService();
+
+ private static User CreateEntityFromMock(MockUser user)
+ {
+ return new User
+ {
+ Name = user.Username,
+ EncryptedPassword = PasswordService.HashPassword(user.Password),
+ RoleString = UserRoleConvert.ToString(user.Administrator),
+ Avatar = null
+ };
+ }
+
+ private static IEnumerable<User> CreateDefaultMockEntities()
+ {
+ // emmmmmmm. Never reuse the user instances because EF Core uses them, which will cause strange things.
+ yield return CreateEntityFromMock(MockUser.User);
+ yield return CreateEntityFromMock(MockUser.Admin);
+ }
+
+ private static void InitDatabase(DatabaseContext context)
+ {
+ context.Database.EnsureCreated();
+ context.Users.AddRange(CreateDefaultMockEntities());
+ context.SaveChanges();
+ }
+
+ public SqliteConnection Connection { get; }
+ public DatabaseContext Context { get; }
+
+ public TestDatabase()
+ {
+ Connection = new SqliteConnection("Data Source=:memory:;");
+ Connection.Open();
+
+ var options = new DbContextOptionsBuilder<DatabaseContext>()
+ .UseSqlite(Connection)
+ .Options;
+
+ Context = new DatabaseContext(options);
+
+ InitDatabase(Context);
+ }
+
+ private List<MockUser> _extraMockUsers;
+
+ public IReadOnlyList<MockUser> ExtraMockUsers => _extraMockUsers;
+
+ public void CreateExtraMockUsers(int count)
+ {
+ if (count <= 0)
+ throw new ArgumentOutOfRangeException(nameof(count), count, "Additional user count must be bigger than 0.");
+ if (_extraMockUsers != null)
+ throw new InvalidOperationException("Already create mock users.");
+
+ _extraMockUsers = new List<MockUser>();
+ for (int i = 0; i < count; i++)
+ {
+ _extraMockUsers.Add(new MockUser($"user{i}", $"password", false));
+ }
+
+ Context.AddRange(_extraMockUsers.Select(u => CreateEntityFromMock(u)));
+ Context.SaveChanges();
+ }
+
+ public void Dispose()
+ {
+ Context.Dispose();
+
+ Connection.Close();
+ Connection.Dispose();
+ }
+
+ }
+}
diff --git a/Timeline.Tests/Helpers/UseCultureAttribute.cs b/Timeline.Tests/Helpers/UseCultureAttribute.cs
index f0064c01..017d77a8 100644
--- a/Timeline.Tests/Helpers/UseCultureAttribute.cs
+++ b/Timeline.Tests/Helpers/UseCultureAttribute.cs
@@ -1,91 +1,94 @@
using System;
using System.Globalization;
-using System.Linq;
using System.Reflection;
using System.Threading;
using Xunit.Sdk;
-// Copied from https://github.com/xunit/samples.xunit/blob/master/UseCulture/UseCultureAttribute.cs
-/// <summary>
-/// Apply this attribute to your test method to replace the
-/// <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and
-/// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
-/// </summary>
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-public class UseCultureAttribute : BeforeAfterTestAttribute
+namespace Timeline.Tests.Helpers
{
- readonly Lazy<CultureInfo> culture;
- readonly Lazy<CultureInfo> uiCulture;
-
- CultureInfo originalCulture;
- CultureInfo originalUICulture;
+ // Copied from https://github.com/xunit/samples.xunit/blob/master/UseCulture/UseCultureAttribute.cs
/// <summary>
- /// Replaces the culture and UI culture of the current thread with
- /// <paramref name="culture" />
+ /// Apply this attribute to your test method to replace the
+ /// <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and
+ /// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// </summary>
- /// <param name="culture">The name of the culture.</param>
- /// <remarks>
- /// <para>
- /// This constructor overload uses <paramref name="culture" /> for both
- /// <see cref="Culture" /> and <see cref="UICulture" />.
- /// </para>
- /// </remarks>
- public UseCultureAttribute(string culture)
- : this(culture, culture) { }
-
- /// <summary>
- /// Replaces the culture and UI culture of the current thread with
- /// <paramref name="culture" /> and <paramref name="uiCulture" />
- /// </summary>
- /// <param name="culture">The name of the culture.</param>
- /// <param name="uiCulture">The name of the UI culture.</param>
- public UseCultureAttribute(string culture, string uiCulture)
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class UseCultureAttribute : BeforeAfterTestAttribute
{
- this.culture = new Lazy<CultureInfo>(() => new CultureInfo(culture, false));
- this.uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture, false));
- }
+ readonly Lazy<CultureInfo> culture;
+ readonly Lazy<CultureInfo> uiCulture;
- /// <summary>
- /// Gets the culture.
- /// </summary>
- public CultureInfo Culture { get { return culture.Value; } }
+ CultureInfo originalCulture;
+ CultureInfo originalUICulture;
- /// <summary>
- /// Gets the UI culture.
- /// </summary>
- public CultureInfo UICulture { get { return uiCulture.Value; } }
+ /// <summary>
+ /// Replaces the culture and UI culture of the current thread with
+ /// <paramref name="culture" />
+ /// </summary>
+ /// <param name="culture">The name of the culture.</param>
+ /// <remarks>
+ /// <para>
+ /// This constructor overload uses <paramref name="culture" /> for both
+ /// <see cref="Culture" /> and <see cref="UICulture" />.
+ /// </para>
+ /// </remarks>
+ public UseCultureAttribute(string culture)
+ : this(culture, culture) { }
- /// <summary>
- /// Stores the current <see cref="Thread.CurrentPrincipal" />
- /// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" />
- /// and replaces them with the new cultures defined in the constructor.
- /// </summary>
- /// <param name="methodUnderTest">The method under test</param>
- public override void Before(MethodInfo methodUnderTest)
- {
- originalCulture = Thread.CurrentThread.CurrentCulture;
- originalUICulture = Thread.CurrentThread.CurrentUICulture;
+ /// <summary>
+ /// Replaces the culture and UI culture of the current thread with
+ /// <paramref name="culture" /> and <paramref name="uiCulture" />
+ /// </summary>
+ /// <param name="culture">The name of the culture.</param>
+ /// <param name="uiCulture">The name of the UI culture.</param>
+ public UseCultureAttribute(string culture, string uiCulture)
+ {
+ this.culture = new Lazy<CultureInfo>(() => new CultureInfo(culture, false));
+ this.uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture, false));
+ }
- Thread.CurrentThread.CurrentCulture = Culture;
- Thread.CurrentThread.CurrentUICulture = UICulture;
+ /// <summary>
+ /// Gets the culture.
+ /// </summary>
+ public CultureInfo Culture { get { return culture.Value; } }
- CultureInfo.CurrentCulture.ClearCachedData();
- CultureInfo.CurrentUICulture.ClearCachedData();
- }
+ /// <summary>
+ /// Gets the UI culture.
+ /// </summary>
+ public CultureInfo UICulture { get { return uiCulture.Value; } }
- /// <summary>
- /// Restores the original <see cref="CultureInfo.CurrentCulture" /> and
- /// <see cref="CultureInfo.CurrentUICulture" /> to <see cref="Thread.CurrentPrincipal" />
- /// </summary>
- /// <param name="methodUnderTest">The method under test</param>
- public override void After(MethodInfo methodUnderTest)
- {
- Thread.CurrentThread.CurrentCulture = originalCulture;
- Thread.CurrentThread.CurrentUICulture = originalUICulture;
+ /// <summary>
+ /// Stores the current <see cref="Thread.CurrentPrincipal" />
+ /// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" />
+ /// and replaces them with the new cultures defined in the constructor.
+ /// </summary>
+ /// <param name="methodUnderTest">The method under test</param>
+ public override void Before(MethodInfo methodUnderTest)
+ {
+ originalCulture = Thread.CurrentThread.CurrentCulture;
+ originalUICulture = Thread.CurrentThread.CurrentUICulture;
+
+ Thread.CurrentThread.CurrentCulture = Culture;
+ Thread.CurrentThread.CurrentUICulture = UICulture;
+
+ CultureInfo.CurrentCulture.ClearCachedData();
+ CultureInfo.CurrentUICulture.ClearCachedData();
+ }
+
+ /// <summary>
+ /// Restores the original <see cref="CultureInfo.CurrentCulture" /> and
+ /// <see cref="CultureInfo.CurrentUICulture" /> to <see cref="Thread.CurrentPrincipal" />
+ /// </summary>
+ /// <param name="methodUnderTest">The method under test</param>
+ public override void After(MethodInfo methodUnderTest)
+ {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ Thread.CurrentThread.CurrentUICulture = originalUICulture;
- CultureInfo.CurrentCulture.ClearCachedData();
- CultureInfo.CurrentUICulture.ClearCachedData();
+ CultureInfo.CurrentCulture.ClearCachedData();
+ CultureInfo.CurrentUICulture.ClearCachedData();
+ }
}
}
diff --git a/Timeline.Tests/IntegratedTests/AuthorizationTest.cs b/Timeline.Tests/IntegratedTests/AuthorizationTest.cs
index a31d98f5..0bc094af 100644
--- a/Timeline.Tests/IntegratedTests/AuthorizationTest.cs
+++ b/Timeline.Tests/IntegratedTests/AuthorizationTest.cs
@@ -1,28 +1,17 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
-using System;
using System.Net;
using System.Threading.Tasks;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
using Xunit;
namespace Timeline.Tests.IntegratedTests
{
- public class AuthorizationTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class AuthorizationTest : IntegratedTestBase
{
- private readonly TestApplication _testApp;
- private readonly WebApplicationFactory<Startup> _factory;
-
public AuthorizationTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
{
- _testApp = new TestApplication(factory);
- _factory = _testApp.Factory;
- }
-
- public void Dispose()
- {
- _testApp.Dispose();
}
private const string BaseUrl = "testing/auth/";
@@ -33,7 +22,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task UnauthenticationTest()
{
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
var response = await client.GetAsync(AuthorizeUrl);
response.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
}
@@ -41,7 +30,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task AuthenticationTest()
{
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
var response = await client.GetAsync(AuthorizeUrl);
response.Should().HaveStatusCode(HttpStatusCode.OK);
}
@@ -49,7 +38,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task UserAuthorizationTest()
{
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
var response1 = await client.GetAsync(UserUrl);
response1.Should().HaveStatusCode(HttpStatusCode.OK);
var response2 = await client.GetAsync(AdminUrl);
@@ -59,7 +48,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task AdminAuthorizationTest()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var response1 = await client.GetAsync(UserUrl);
response1.Should().HaveStatusCode(HttpStatusCode.OK);
var response2 = await client.GetAsync(AdminUrl);
diff --git a/Timeline.Tests/IntegratedTests/I18nTest.cs b/Timeline.Tests/IntegratedTests/I18nTest.cs
index 67bbea5c..855179af 100644
--- a/Timeline.Tests/IntegratedTests/I18nTest.cs
+++ b/Timeline.Tests/IntegratedTests/I18nTest.cs
@@ -1,32 +1,28 @@
-using Microsoft.AspNetCore.Mvc.Testing;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Timeline.Tests.Helpers;
using Xunit;
-using FluentAssertions;
namespace Timeline.Tests.IntegratedTests
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1054:Uri parameters should not be strings")]
- public class I18nTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class I18nTest : IntegratedTestBase
{
- private readonly TestApplication _testApp;
private readonly HttpClient _client;
public I18nTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
{
- _testApp = new TestApplication(factory);
- _client = _testApp.Factory.CreateDefaultClient();
+ _client = Factory.CreateDefaultClient();
}
- public void Dispose()
+ protected override void OnDispose()
{
_client.Dispose();
- _testApp.Dispose();
}
private const string DirectUrl = "testing/i18n/direct";
diff --git a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs
new file mode 100644
index 00000000..242a452d
--- /dev/null
+++ b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs
@@ -0,0 +1,94 @@
+using Microsoft.AspNetCore.Mvc.Testing;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Timeline.Tests.Helpers;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public enum AuthType
+ {
+ None,
+ User,
+ Admin
+ }
+
+ public static class AuthTypeExtensions
+ {
+ public static MockUser GetMockUser(this AuthType authType)
+ {
+ return authType switch
+ {
+ AuthType.None => null,
+ AuthType.User => MockUser.User,
+ AuthType.Admin => MockUser.Admin,
+ _ => throw new InvalidOperationException("Unknown auth type.")
+ };
+ }
+
+ public static string GetUsername(this AuthType authType) => authType.GetMockUser().Username;
+ }
+
+ public abstract class IntegratedTestBase : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ {
+ protected TestApplication TestApp { get; }
+
+ protected WebApplicationFactory<Startup> Factory => TestApp.Factory;
+
+ public IntegratedTestBase(WebApplicationFactory<Startup> factory)
+ {
+ TestApp = new TestApplication(factory);
+ }
+
+ protected virtual void OnDispose()
+ {
+
+ }
+
+ public void Dispose()
+ {
+ OnDispose();
+ TestApp.Dispose();
+ }
+
+ protected void CreateExtraMockUsers(int count)
+ {
+ TestApp.Database.CreateExtraMockUsers(count);
+ }
+
+ protected IReadOnlyList<MockUser> ExtraMockUsers => TestApp.Database.ExtraMockUsers;
+
+ public Task<HttpClient> CreateClientWithNoAuth()
+ {
+ return Task.FromResult(Factory.CreateDefaultClient());
+ }
+
+ public async Task<HttpClient> CreateClientWithCredential(string username, string password)
+ {
+ var client = Factory.CreateDefaultClient();
+ var response = await client.PostAsJsonAsync("/token/create",
+ new CreateTokenRequest { Username = username, Password = password });
+ var token = response.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<CreateTokenResponse>().Which.Token;
+ client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
+ return client;
+ }
+
+ public Task<HttpClient> CreateClientAs(MockUser user)
+ {
+ if (user == null)
+ return CreateClientWithNoAuth();
+ return CreateClientWithCredential(user.Username, user.Password);
+ }
+
+ public Task<HttpClient> CreateClientAs(AuthType authType) => CreateClientAs(authType.GetMockUser());
+
+
+ public Task<HttpClient> CreateClientAsUser() => CreateClientAs(MockUser.User);
+ public Task<HttpClient> CreateClientAsAdmin() => CreateClientAs(MockUser.Admin);
+
+ }
+}
diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs
new file mode 100644
index 00000000..483499fb
--- /dev/null
+++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs
@@ -0,0 +1,495 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Timeline.Models;
+using Timeline.Models.Http;
+using Timeline.Tests.Helpers;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class PersonalTimelineTest : IntegratedTestBase
+ {
+ public PersonalTimelineTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
+ {
+
+ }
+
+ [Fact]
+ public async Task TimelineGet_Should_Work()
+ {
+ using var client = await CreateClientWithNoAuth();
+ var res = await client.GetAsync("users/user/timeline");
+ var body = res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<BaseTimelineInfo>().Which;
+ body.Owner.Should().Be("user");
+ body.Visibility.Should().Be(TimelineVisibility.Register);
+ body.Description.Should().Be("");
+ body.Members.Should().NotBeNull().And.BeEmpty();
+ }
+
+ [Fact]
+ public async Task Description_Should_Work()
+ {
+ using var client = await CreateClientAsUser();
+
+ async Task AssertDescription(string description)
+ {
+ var res = await client.GetAsync("users/user/timeline");
+ var body = res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<BaseTimelineInfo>()
+ .Which.Description.Should().Be(description);
+ }
+
+ const string mockDescription = "haha";
+
+ await AssertDescription("");
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/property",
+ new TimelinePropertyChangeRequest { Description = mockDescription });
+ res.Should().HaveStatusCode(200);
+ await AssertDescription(mockDescription);
+ }
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/property",
+ new TimelinePropertyChangeRequest { Description = null });
+ res.Should().HaveStatusCode(200);
+ await AssertDescription(mockDescription);
+ }
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/property",
+ new TimelinePropertyChangeRequest { Description = "" });
+ res.Should().HaveStatusCode(200);
+ await AssertDescription("");
+ }
+ }
+
+ [Fact]
+ public async Task Member_Should_Work()
+ {
+ const string getUrl = "users/user/timeline";
+ const string changeUrl = "users/user/timeline/op/member";
+ using var client = await CreateClientAsUser();
+
+ async Task AssertMembers(IList<string> members)
+ {
+ var res = await client.GetAsync(getUrl);
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<BaseTimelineInfo>()
+ .Which.Members.Should().NotBeNull().And.BeEquivalentTo(members);
+ }
+
+ async Task AssertEmptyMembers()
+ {
+ var res = await client.GetAsync(getUrl);
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<BaseTimelineInfo>()
+ .Which.Members.Should().NotBeNull().And.BeEmpty();
+ }
+
+ await AssertEmptyMembers();
+ {
+ var res = await client.PostAsJsonAsync(changeUrl,
+ new TimelineMemberChangeRequest { Add = new List<string> { "admin", "usernotexist" } });
+ res.Should().HaveStatusCode(400)
+ .And.HaveCommonBody()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.ChangeMemberUserNotExist);
+ }
+ {
+ var res = await client.PostAsJsonAsync(changeUrl,
+ new TimelineMemberChangeRequest { Remove = new List<string> { "admin", "usernotexist" } });
+ res.Should().HaveStatusCode(400)
+ .And.HaveCommonBody()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.ChangeMemberUserNotExist);
+ }
+ {
+ var res = await client.PostAsJsonAsync(changeUrl,
+ new TimelineMemberChangeRequest { Add = new List<string> { "admin" }, Remove = new List<string> { "admin" } });
+ res.Should().HaveStatusCode(200);
+ await AssertEmptyMembers();
+ }
+ {
+ var res = await client.PostAsJsonAsync(changeUrl,
+ new TimelineMemberChangeRequest { Add = new List<string> { "admin" } });
+ res.Should().HaveStatusCode(200);
+ await AssertMembers(new List<string> { "admin" });
+ }
+ {
+ var res = await client.PostAsJsonAsync(changeUrl,
+ new TimelineMemberChangeRequest { Remove = new List<string> { "admin" } });
+ res.Should().HaveStatusCode(200);
+ await AssertEmptyMembers();
+ }
+ }
+
+ [Theory]
+ [InlineData(AuthType.None, 200, 401, 401, 401, 401)]
+ [InlineData(AuthType.User, 200, 200, 403, 200, 403)]
+ [InlineData(AuthType.Admin, 200, 200, 200, 200, 200)]
+ public async Task Permission_Timeline(AuthType authType, int get, int opPropertyUser, int opPropertyAdmin, int opMemberUser, int opMemberAdmin)
+ {
+ using var client = await CreateClientAs(authType);
+ {
+ var res = await client.GetAsync("users/user/timeline");
+ res.Should().HaveStatusCode(get);
+ }
+
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/property",
+ new TimelinePropertyChangeRequest { Description = "hahaha" });
+ res.Should().HaveStatusCode(opPropertyUser);
+ }
+
+ {
+ var res = await client.PostAsJsonAsync("users/admin/timeline/op/property",
+ new TimelinePropertyChangeRequest { Description = "hahaha" });
+ res.Should().HaveStatusCode(opPropertyAdmin);
+ }
+
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/member",
+ new TimelineMemberChangeRequest { Add = new List<string> { "admin" } });
+ res.Should().HaveStatusCode(opMemberUser);
+ }
+
+ {
+ var res = await client.PostAsJsonAsync("users/admin/timeline/op/member",
+ new TimelineMemberChangeRequest { Add = new List<string> { "user" } });
+ res.Should().HaveStatusCode(opMemberAdmin);
+ }
+ }
+
+ [Fact]
+ public async Task Permission_GetPost()
+ {
+ const string userUrl = "users/user/timeline/posts";
+ const string adminUrl = "users/admin/timeline/posts";
+ { // default visibility is registered
+ {
+ using var client = await CreateClientWithNoAuth();
+ var res = await client.GetAsync(userUrl);
+ res.Should().HaveStatusCode(403);
+ }
+
+ {
+ using var client = await CreateClientAsUser();
+ var res = await client.GetAsync(adminUrl);
+ res.Should().HaveStatusCode(200);
+ }
+ }
+
+ { // change visibility to public
+ {
+ using var client = await CreateClientAsUser();
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/property",
+ new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Public });
+ res.Should().HaveStatusCode(200);
+ }
+ {
+ using var client = await CreateClientWithNoAuth();
+ var res = await client.GetAsync(userUrl);
+ res.Should().HaveStatusCode(200);
+ }
+ }
+
+ { // change visibility to private
+ {
+ using var client = await CreateClientAsAdmin();
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/property",
+ new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Private });
+ res.Should().HaveStatusCode(200);
+ }
+ {
+ var res = await client.PostAsJsonAsync("users/admin/timeline/op/property",
+ new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Private });
+ res.Should().HaveStatusCode(200);
+ }
+ }
+ {
+ using var client = await CreateClientWithNoAuth();
+ var res = await client.GetAsync(userUrl);
+ res.Should().HaveStatusCode(403);
+ }
+ { // user can't read admin's
+ using var client = await CreateClientAsUser();
+ var res = await client.GetAsync(adminUrl);
+ res.Should().HaveStatusCode(403);
+ }
+ { // admin can read user's
+ using var client = await CreateClientAsAdmin();
+ var res = await client.GetAsync(userUrl);
+ res.Should().HaveStatusCode(200);
+ }
+ { // add member
+ using var client = await CreateClientAsAdmin();
+ var res = await client.PostAsJsonAsync("users/admin/timeline/op/member",
+ new TimelineMemberChangeRequest { Add = new List<string> { "user" } });
+ res.Should().HaveStatusCode(200);
+ }
+ { // now user can read admin's
+ using var client = await CreateClientAsUser();
+ var res = await client.GetAsync(adminUrl);
+ res.Should().HaveStatusCode(200);
+ }
+ }
+ }
+
+
+ [Fact]
+ public async Task Permission_Post_Create()
+ {
+ CreateExtraMockUsers(1);
+
+ using (var client = await CreateClientAsUser())
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/member",
+ new TimelineMemberChangeRequest { Add = new List<string> { "user0" } });
+ res.Should().HaveStatusCode(200);
+ }
+
+ using (var client = await CreateClientWithNoAuth())
+ {
+ { // no auth should get 401
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa" });
+ res.Should().HaveStatusCode(401);
+ }
+ }
+
+ using (var client = await CreateClientAsUser())
+ {
+ { // post self's
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa" });
+ res.Should().HaveStatusCode(200);
+ }
+ { // post other not as a member should get 403
+ var res = await client.PostAsJsonAsync("users/admin/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa" });
+ res.Should().HaveStatusCode(403);
+ }
+ }
+
+ using (var client = await CreateClientAsAdmin())
+ {
+ { // post as admin
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa" });
+ res.Should().HaveStatusCode(200);
+ }
+ }
+
+ using (var client = await CreateClientAs(ExtraMockUsers[0]))
+ {
+ { // post as member
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa" });
+ res.Should().HaveStatusCode(200);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Permission_Post_Delete()
+ {
+ CreateExtraMockUsers(2);
+
+ async Task<long> CreatePost(MockUser auth, string timeline)
+ {
+ using var client = await CreateClientAs(auth);
+ var res = await client.PostAsJsonAsync($"users/{timeline}/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa" });
+ return res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostCreateResponse>()
+ .Which.Id;
+ }
+
+ using (var client = await CreateClientAsUser())
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/op/member",
+ new TimelineMemberChangeRequest { Add = new List<string> { "user0", "user1" } });
+ res.Should().HaveStatusCode(200);
+ }
+
+ { // no auth should get 401
+ using var client = await CreateClientWithNoAuth();
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = 12 });
+ res.Should().HaveStatusCode(401);
+ }
+
+ { // self can delete self
+ var postId = await CreatePost(MockUser.User, "user");
+ using var client = await CreateClientAsUser();
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = postId });
+ res.Should().HaveStatusCode(200);
+ }
+
+ { // admin can delete any
+ var postId = await CreatePost(MockUser.User, "user");
+ using var client = await CreateClientAsAdmin();
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = postId });
+ res.Should().HaveStatusCode(200);
+ }
+
+ { // owner can delete other
+ var postId = await CreatePost(ExtraMockUsers[0], "user");
+ using var client = await CreateClientAsUser();
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = postId });
+ res.Should().HaveStatusCode(200);
+ }
+
+ { // author can delete self
+ var postId = await CreatePost(ExtraMockUsers[0], "user");
+ using var client = await CreateClientAs(ExtraMockUsers[0]);
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = postId });
+ res.Should().HaveStatusCode(200);
+ }
+
+ { // otherwise is forbidden
+ var postId = await CreatePost(ExtraMockUsers[0], "user");
+ using var client = await CreateClientAs(ExtraMockUsers[1]);
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = postId });
+ res.Should().HaveStatusCode(403);
+ }
+ }
+
+ [Fact]
+ public async Task Post_Op_Should_Work()
+ {
+ {
+ using var client = await CreateClientAsUser();
+ {
+ var res = await client.GetAsync("users/user/timeline/posts");
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostInfo[]>()
+ .Which.Should().NotBeNull().And.BeEmpty();
+ }
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = null });
+ res.Should().BeInvalidModel();
+ }
+ const string mockContent = "aaa";
+ TimelinePostCreateResponse createRes;
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = mockContent });
+ var body = res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostCreateResponse>()
+ .Which;
+ body.Should().NotBeNull();
+ createRes = body;
+ }
+ {
+ var res = await client.GetAsync("users/user/timeline/posts");
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostInfo[]>()
+ .Which.Should().NotBeNull().And.BeEquivalentTo(
+ new TimelinePostInfo
+ {
+ Id = createRes.Id,
+ Author = "user",
+ Content = mockContent,
+ Time = createRes.Time
+ });
+ }
+ const string mockContent2 = "bbb";
+ var mockTime2 = DateTime.Now.AddDays(-1);
+ TimelinePostCreateResponse createRes2;
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = mockContent2, Time = mockTime2 });
+ var body = res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostCreateResponse>()
+ .Which;
+ body.Should().NotBeNull();
+ createRes2 = body;
+ }
+ {
+ var res = await client.GetAsync("users/user/timeline/posts");
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostInfo[]>()
+ .Which.Should().NotBeNull().And.BeEquivalentTo(
+ new TimelinePostInfo
+ {
+ Id = createRes.Id,
+ Author = "user",
+ Content = mockContent,
+ Time = createRes.Time
+ },
+ new TimelinePostInfo
+ {
+ Id = createRes2.Id,
+ Author = "user",
+ Content = mockContent2,
+ Time = createRes2.Time
+ });
+ }
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = createRes.Id });
+ res.Should().HaveStatusCode(200);
+ }
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/delete",
+ new TimelinePostDeleteRequest { Id = 30000 });
+ res.Should().HaveStatusCode(400)
+ .And.HaveCommonBody()
+ .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationDeleteNotExist);
+ }
+ {
+ var res = await client.GetAsync("users/user/timeline/posts");
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostInfo[]>()
+ .Which.Should().NotBeNull().And.BeEquivalentTo(
+ new TimelinePostInfo
+ {
+ Id = createRes2.Id,
+ Author = "user",
+ Content = mockContent2,
+ Time = createRes2.Time
+ });
+ }
+ }
+ }
+
+ [Fact]
+ public async Task GetPost_Should_Ordered()
+ {
+ using var client = await CreateClientAsUser();
+
+ async Task<long> CreatePost(DateTime time)
+ {
+ var res = await client.PostAsJsonAsync("users/user/timeline/postop/create",
+ new TimelinePostCreateRequest { Content = "aaa", Time = time });
+ return res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostCreateResponse>()
+ .Which.Id;
+ }
+
+ var now = DateTime.Now;
+ var id0 = await CreatePost(now.AddDays(1));
+ var id1 = await CreatePost(now.AddDays(-1));
+ var id2 = await CreatePost(now);
+
+ {
+ var res = await client.GetAsync("users/user/timeline/posts");
+ res.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<TimelinePostInfo[]>()
+ .Which.Select(p => p.Id).Should().Equal(id1, id2, id0);
+ }
+ }
+ }
+}
diff --git a/Timeline.Tests/IntegratedTests/TokenTest.cs b/Timeline.Tests/IntegratedTests/TokenTest.cs
index 111e8d8e..e62228fc 100644
--- a/Timeline.Tests/IntegratedTests/TokenTest.cs
+++ b/Timeline.Tests/IntegratedTests/TokenTest.cs
@@ -1,37 +1,33 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
-using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Timeline.Tests.Mock.Data;
using Xunit;
using static Timeline.ErrorCodes.Http.Token;
namespace Timeline.Tests.IntegratedTests
{
- public class TokenTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class TokenTest : IntegratedTestBase
{
private const string CreateTokenUrl = "token/create";
private const string VerifyTokenUrl = "token/verify";
- private readonly TestApplication _testApp;
- private readonly WebApplicationFactory<Startup> _factory;
-
public TokenTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
{
- _testApp = new TestApplication(factory);
- _factory = _testApp.Factory;
+
}
- public void Dispose()
+ private static async Task<CreateTokenResponse> CreateUserTokenAsync(HttpClient client, string username, string password, int? expireOffset = null)
{
- _testApp.Dispose();
+ var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, Expire = expireOffset });
+ return response.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<CreateTokenResponse>().Which;
}
public static IEnumerable<object[]> CreateToken_InvalidModel_Data()
@@ -46,7 +42,7 @@ namespace Timeline.Tests.IntegratedTests
[MemberData(nameof(CreateToken_InvalidModel_Data))]
public async Task CreateToken_InvalidModel(string username, string password, int expire)
{
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
(await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest
{
Username = username,
@@ -65,7 +61,7 @@ namespace Timeline.Tests.IntegratedTests
[MemberData(nameof(CreateToken_UserCredential_Data))]
public async void CreateToken_UserCredential(string username, string password)
{
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
var response = await client.PostAsJsonAsync(CreateTokenUrl,
new CreateTokenRequest { Username = username, Password = password });
response.Should().HaveStatusCode(400)
@@ -76,7 +72,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task CreateToken_Success()
{
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
var response = await client.PostAsJsonAsync(CreateTokenUrl,
new CreateTokenRequest { Username = MockUser.User.Username, Password = MockUser.User.Password });
var body = response.Should().HaveStatusCode(200)
@@ -88,7 +84,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task VerifyToken_InvalidModel()
{
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
(await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = null })).Should().BeInvalidModel();
}
@@ -96,7 +92,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task VerifyToken_BadFormat()
{
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
var response = await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = "bad token hahaha" });
response.Should().HaveStatusCode(400)
@@ -107,10 +103,10 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task VerifyToken_OldVersion()
{
- using var client = _factory.CreateDefaultClient();
- var token = (await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password)).Token;
+ using var client = await CreateClientWithNoAuth();
+ var token = (await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password)).Token;
- using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
+ using (var scope = Factory.Server.Host.Services.CreateScope()) // UserService is scoped.
{
// create a user for test
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
@@ -127,10 +123,10 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task VerifyToken_UserNotExist()
{
- using var client = _factory.CreateDefaultClient();
- var token = (await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password)).Token;
+ using var client = await CreateClientWithNoAuth();
+ var token = (await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password)).Token;
- using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
+ using (var scope = Factory.Server.Host.Services.CreateScope()) // UserService is scoped.
{
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
await userService.DeleteUser(MockUser.User.Username);
@@ -146,7 +142,7 @@ namespace Timeline.Tests.IntegratedTests
//[Fact]
//public async Task VerifyToken_Expired()
//{
- // using (var client = _factory.CreateDefaultClient())
+ // using (var client = await CreateClientWithNoAuth())
// {
// // I can only control the token expired time but not current time
// // because verify logic is encapsuled in other library.
@@ -164,8 +160,8 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task VerifyToken_Success()
{
- using var client = _factory.CreateDefaultClient();
- var createTokenResult = await client.CreateUserTokenAsync(MockUser.User.Username, MockUser.User.Password);
+ using var client = await CreateClientWithNoAuth();
+ var createTokenResult = await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password);
var response = await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = createTokenResult.Token });
response.Should().HaveStatusCode(200)
diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
index 2310fc66..25a7b675 100644
--- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
@@ -15,27 +15,18 @@ using System.Net.Http.Headers;
using System.Threading.Tasks;
using Timeline.Services;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
using Xunit;
using static Timeline.ErrorCodes.Http.Common;
using static Timeline.ErrorCodes.Http.UserAvatar;
namespace Timeline.Tests.IntegratedTests
{
- public class UserAvatarTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class UserAvatarTest : IntegratedTestBase
{
- private readonly TestApplication _testApp;
- private readonly WebApplicationFactory<Startup> _factory;
-
public UserAvatarTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
{
- _testApp = new TestApplication(factory);
- _factory = _testApp.Factory;
- }
- public void Dispose()
- {
- _testApp.Dispose();
}
[Fact]
@@ -48,7 +39,7 @@ namespace Timeline.Tests.IntegratedTests
Type = PngFormat.Instance.DefaultMimeType
};
- using (var client = await _factory.CreateClientAsUser())
+ using (var client = await CreateClientAsUser())
{
{
var res = await client.GetAsync("users/usernotexist/avatar");
@@ -57,7 +48,7 @@ namespace Timeline.Tests.IntegratedTests
.Which.Code.Should().Be(Get.UserNotExist);
}
- var env = _factory.Server.Host.Services.GetRequiredService<IWebHostEnvironment>();
+ var env = Factory.Server.Host.Services.GetRequiredService<IWebHostEnvironment>();
var defaultAvatarData = await File.ReadAllBytesAsync(Path.Combine(env.ContentRootPath, "default-avatar.png"));
async Task GetReturnDefault(string username = "user")
@@ -239,7 +230,7 @@ namespace Timeline.Tests.IntegratedTests
}
// Authorization check.
- using (var client = await _factory.CreateClientAsAdmin())
+ using (var client = await CreateClientAsAdmin())
{
{
var res = await client.PutByteArrayAsync("users/user/avatar", mockAvatar.Data, mockAvatar.Type);
@@ -266,7 +257,7 @@ namespace Timeline.Tests.IntegratedTests
}
// bad username check
- using (var client = await _factory.CreateClientAsAdmin())
+ using (var client = await CreateClientAsAdmin())
{
{
var res = await client.GetAsync("users/u!ser/avatar");
diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
index 8f2b6925..932c287e 100644
--- a/Timeline.Tests/IntegratedTests/UserDetailTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
@@ -1,38 +1,27 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
-using System;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Threading.Tasks;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Timeline.Tests.Mock.Data;
using Xunit;
namespace Timeline.Tests.IntegratedTests
{
- public class UserDetailTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class UserDetailTest : IntegratedTestBase
{
- private readonly TestApplication _testApp;
- private readonly WebApplicationFactory<Startup> _factory;
-
public UserDetailTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
{
- _testApp = new TestApplication(factory);
- _factory = _testApp.Factory;
- }
- public void Dispose()
- {
- _testApp.Dispose();
}
[Fact]
public async Task PermissionTest()
{
{ // unauthorize
- using var client = _factory.CreateDefaultClient();
+ using var client = await CreateClientWithNoAuth();
{ // GET
var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
res.Should().HaveStatusCode(HttpStatusCode.OK);
@@ -47,7 +36,7 @@ namespace Timeline.Tests.IntegratedTests
}
}
{ // user
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
{ // GET
var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
res.Should().HaveStatusCode(HttpStatusCode.OK);
@@ -70,7 +59,7 @@ namespace Timeline.Tests.IntegratedTests
}
}
{ // user
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
{ // PUT other
var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
res.Should().HaveStatusCode(HttpStatusCode.OK);
@@ -88,7 +77,7 @@ namespace Timeline.Tests.IntegratedTests
var url = $"users/{MockUser.User.Username}/nickname";
var userNotExistUrl = "users/usernotexist/nickname";
{
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
{
var res = await client.GetAsync(userNotExistUrl);
res.Should().HaveStatusCode(HttpStatusCode.NotFound)
@@ -134,7 +123,7 @@ namespace Timeline.Tests.IntegratedTests
}
}
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
{
var res = await client.PutStringAsync(userNotExistUrl, "aaa");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
diff --git a/Timeline.Tests/IntegratedTests/UserTest.cs b/Timeline.Tests/IntegratedTests/UserTest.cs
index 7e99ddba..abfea18e 100644
--- a/Timeline.Tests/IntegratedTests/UserTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserTest.cs
@@ -1,39 +1,28 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
-using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Helpers.Authentication;
-using Timeline.Tests.Mock.Data;
using Xunit;
using static Timeline.ErrorCodes.Http.User;
namespace Timeline.Tests.IntegratedTests
{
- public class UserTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class UserTest : IntegratedTestBase
{
- private readonly TestApplication _testApp;
- private readonly WebApplicationFactory<Startup> _factory;
-
public UserTest(WebApplicationFactory<Startup> factory)
+ : base(factory)
{
- _testApp = new TestApplication(factory);
- _factory = _testApp.Factory;
- }
- public void Dispose()
- {
- _testApp.Dispose();
}
[Fact]
public async Task Get_List_Success()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.GetAsync("users");
res.Should().HaveStatusCode(200)
.And.HaveJsonBody<UserInfo[]>()
@@ -43,7 +32,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Get_Single_Success()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.GetAsync("users/" + MockUser.User.Username);
res.Should().HaveStatusCode(200)
.And.HaveJsonBody<UserInfo>()
@@ -53,7 +42,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Get_InvalidModel()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.GetAsync("users/aaa!a");
res.Should().BeInvalidModel();
}
@@ -61,7 +50,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Get_Users_404()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.GetAsync("users/usernotexist");
res.Should().HaveStatusCode(404)
.And.HaveCommonBody()
@@ -79,7 +68,7 @@ namespace Timeline.Tests.IntegratedTests
[MemberData(nameof(Put_InvalidModel_Data))]
public async Task Put_InvalidModel(string username, string password, bool? administrator)
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
(await client.PutAsJsonAsync("users/" + username,
new UserPutRequest { Password = password, Administrator = administrator }))
.Should().BeInvalidModel();
@@ -96,7 +85,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Put_Modiefied()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.PutAsJsonAsync("users/" + MockUser.User.Username, new UserPutRequest
{
Password = "password",
@@ -109,7 +98,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Put_Created()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
const string username = "puttest";
const string url = "users/" + username;
@@ -125,7 +114,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Patch_NotExist()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
res.Should().HaveStatusCode(404)
.And.HaveCommonBody()
@@ -135,7 +124,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Patch_InvalidModel()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.PatchAsJsonAsync("users/aaa!a", new UserPatchRequest { });
res.Should().BeInvalidModel();
}
@@ -143,7 +132,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Patch_Success()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
{
var res = await client.PatchAsJsonAsync("users/" + MockUser.User.Username,
new UserPatchRequest { Administrator = false });
@@ -155,7 +144,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Delete_InvalidModel()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var url = "users/aaa!a";
var res = await client.DeleteAsync(url);
res.Should().BeInvalidModel();
@@ -164,7 +153,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Delete_Deleted()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var url = "users/" + MockUser.User.Username;
var res = await client.DeleteAsync(url);
res.Should().BeDelete(true);
@@ -176,7 +165,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Delete_NotExist()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.DeleteAsync("users/usernotexist");
res.Should().BeDelete(false);
}
@@ -195,7 +184,7 @@ namespace Timeline.Tests.IntegratedTests
[MemberData(nameof(Op_ChangeUsername_InvalidModel_Data))]
public async Task Op_ChangeUsername_InvalidModel(string oldUsername, string newUsername)
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
(await client.PostAsJsonAsync(changeUsernameUrl,
new ChangeUsernameRequest { OldUsername = oldUsername, NewUsername = newUsername }))
.Should().BeInvalidModel();
@@ -204,7 +193,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Op_ChangeUsername_UserNotExist()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.PostAsJsonAsync(changeUsernameUrl,
new ChangeUsernameRequest { OldUsername = "usernotexist", NewUsername = "newUsername" });
res.Should().HaveStatusCode(400)
@@ -215,7 +204,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Op_ChangeUsername_UserAlreadyExist()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
var res = await client.PostAsJsonAsync(changeUsernameUrl,
new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = MockUser.Admin.Username });
res.Should().HaveStatusCode(400)
@@ -223,15 +212,23 @@ namespace Timeline.Tests.IntegratedTests
.Which.Code.Should().Be(Op.ChangeUsername.AlreadyExist);
}
+ private async Task TestLogin(string username, string password)
+ {
+ using var client = await CreateClientWithNoAuth();
+ var response = await client.PostAsJsonAsync("token/create", new CreateTokenRequest { Username = username, Password = password });
+ response.Should().HaveStatusCode(200)
+ .And.HaveJsonBody<CreateTokenResponse>();
+ }
+
[Fact]
public async Task Op_ChangeUsername_Success()
{
- using var client = await _factory.CreateClientAsAdmin();
+ using var client = await CreateClientAsAdmin();
const string newUsername = "hahaha";
var res = await client.PostAsJsonAsync(changeUsernameUrl,
new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = newUsername });
res.Should().HaveStatusCode(200);
- await client.CreateUserTokenAsync(newUsername, MockUser.User.Password);
+ await TestLogin(newUsername, MockUser.User.Password);
}
private const string changePasswordUrl = "userop/changepassword";
@@ -246,7 +243,7 @@ namespace Timeline.Tests.IntegratedTests
[MemberData(nameof(Op_ChangePassword_InvalidModel_Data))]
public async Task Op_ChangePassword_InvalidModel(string oldPassword, string newPassword)
{
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
(await client.PostAsJsonAsync(changePasswordUrl,
new ChangePasswordRequest { OldPassword = oldPassword, NewPassword = newPassword }))
.Should().BeInvalidModel();
@@ -255,7 +252,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Op_ChangePassword_BadOldPassword()
{
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
var res = await client.PostAsJsonAsync(changePasswordUrl, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" });
res.Should().HaveStatusCode(400)
.And.HaveCommonBody()
@@ -265,13 +262,12 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Op_ChangePassword_Success()
{
- using var client = await _factory.CreateClientAsUser();
+ using var client = await CreateClientAsUser();
const string newPassword = "new";
var res = await client.PostAsJsonAsync(changePasswordUrl,
new ChangePasswordRequest { OldPassword = MockUser.User.Password, NewPassword = newPassword });
res.Should().HaveStatusCode(200);
- await _factory.CreateDefaultClient() // don't use client above, because it sets authorization header
- .CreateUserTokenAsync(MockUser.User.Username, newPassword);
+ await TestLogin(MockUser.User.Username, newPassword);
}
}
}
diff --git a/Timeline.Tests/Mock/Data/TestDatabase.cs b/Timeline.Tests/Mock/Data/TestDatabase.cs
deleted file mode 100644
index 1e662546..00000000
--- a/Timeline.Tests/Mock/Data/TestDatabase.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Microsoft.Data.Sqlite;
-using Microsoft.EntityFrameworkCore;
-using System;
-using Timeline.Entities;
-
-namespace Timeline.Tests.Mock.Data
-{
- public class TestDatabase : IDisposable
- {
- public static void InitDatabase(DatabaseContext context)
- {
- context.Database.EnsureCreated();
- context.Users.AddRange(MockUser.CreateMockEntities());
- context.SaveChanges();
- }
-
- public TestDatabase()
- {
- DatabaseConnection = new SqliteConnection("Data Source=:memory:;");
- DatabaseConnection.Open();
-
- var options = new DbContextOptionsBuilder<DatabaseContext>()
- .UseSqlite(DatabaseConnection)
- .Options;
-
- DatabaseContext = new DatabaseContext(options);
-
- InitDatabase(DatabaseContext);
- }
-
- public void Dispose()
- {
- DatabaseContext.Dispose();
-
- DatabaseConnection.Close();
- DatabaseConnection.Dispose();
- }
-
- public SqliteConnection DatabaseConnection { get; }
- public DatabaseContext DatabaseContext { get; }
- }
-}
diff --git a/Timeline.Tests/Mock/Data/TestUsers.cs b/Timeline.Tests/Mock/Data/TestUsers.cs
deleted file mode 100644
index fa75236a..00000000
--- a/Timeline.Tests/Mock/Data/TestUsers.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Timeline.Entities;
-using Timeline.Models;
-using Timeline.Services;
-
-namespace Timeline.Tests.Mock.Data
-{
- public class MockUser
- {
- public MockUser(string username, string password, bool administrator)
- {
- Info = new UserInfo(username, administrator);
- Password = password;
- }
-
- public UserInfo Info { get; set; }
- public string Username => Info.Username;
- public string Password { get; set; }
- public bool Administrator => Info.Administrator;
-
-
- public static MockUser User { get; } = new MockUser("user", "userpassword", false);
- public static MockUser Admin { get; } = new MockUser("admin", "adminpassword", true);
-
- public static IReadOnlyList<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 passwordService = new PasswordService();
- User Create(MockUser user)
- {
- return new User
- {
- Name = user.Username,
- EncryptedPassword = passwordService.HashPassword(user.Password),
- RoleString = UserRoleConvert.ToString(user.Administrator),
- Avatar = null
- };
- }
-
- return new List<User>
- {
- Create(User),
- Create(Admin)
- };
- }
- }
-}
diff --git a/Timeline.Tests/Services/UserAvatarServiceTest.cs b/Timeline.Tests/Services/UserAvatarServiceTest.cs
index cf3d2a0a..2729aa6f 100644
--- a/Timeline.Tests/Services/UserAvatarServiceTest.cs
+++ b/Timeline.Tests/Services/UserAvatarServiceTest.cs
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Services;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Mock.Data;
using Xunit;
namespace Timeline.Tests.Services
@@ -139,7 +138,7 @@ namespace Timeline.Tests.Services
_database = new TestDatabase();
- _service = new UserAvatarService(NullLogger<UserAvatarService>.Instance, _database.DatabaseContext, _mockDefaultAvatarProvider.Object, _mockValidator.Object, _mockETagGenerator.Object, _mockClock.Object);
+ _service = new UserAvatarService(NullLogger<UserAvatarService>.Instance, _database.Context, _mockDefaultAvatarProvider.Object, _mockValidator.Object, _mockETagGenerator.Object, _mockClock.Object);
}
public void Dispose()
@@ -171,7 +170,7 @@ namespace Timeline.Tests.Services
string username = MockUser.User.Username;
var mockAvatarEntity = CreateMockAvatarEntity("aaa");
{
- var context = _database.DatabaseContext;
+ var context = _database.Context;
var user = await context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync();
user.Avatar = mockAvatarEntity;
await context.SaveChangesAsync();
@@ -205,7 +204,7 @@ namespace Timeline.Tests.Services
string username = MockUser.User.Username;
var mockAvatarEntity = CreateMockAvatarEntity("aaa");
{
- var context = _database.DatabaseContext;
+ var context = _database.Context;
var user = await context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync();
user.Avatar = mockAvatarEntity;
await context.SaveChangesAsync();
@@ -237,7 +236,7 @@ namespace Timeline.Tests.Services
{
string username = MockUser.User.Username;
- var user = await _database.DatabaseContext.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync();
+ var user = await _database.Context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync();
var avatar1 = CreateMockAvatar("aaa");
var avatar2 = CreateMockAvatar("bbb");
diff --git a/Timeline.Tests/Services/UserDetailServiceTest.cs b/Timeline.Tests/Services/UserDetailServiceTest.cs
index c7037c6e..9a869c89 100644
--- a/Timeline.Tests/Services/UserDetailServiceTest.cs
+++ b/Timeline.Tests/Services/UserDetailServiceTest.cs
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Services;
using Timeline.Tests.Helpers;
-using Timeline.Tests.Mock.Data;
using Xunit;
namespace Timeline.Tests.Services
@@ -21,7 +20,7 @@ namespace Timeline.Tests.Services
public UserDetailServiceTest()
{
_testDatabase = new TestDatabase();
- _service = new UserDetailService(_testDatabase.DatabaseContext, NullLogger<UserDetailService>.Instance);
+ _service = new UserDetailService(_testDatabase.Context, NullLogger<UserDetailService>.Instance);
}
public void Dispose()
@@ -51,7 +50,7 @@ namespace Timeline.Tests.Services
{
const string nickname = "aaaaaa";
{
- var context = _testDatabase.DatabaseContext;
+ var context = _testDatabase.Context;
var userId = (await context.Users.Where(u => u.Name == MockUser.User.Username).Select(u => new { u.Id }).SingleAsync()).Id;
context.UserDetails.Add(new UserDetail
{
@@ -84,7 +83,7 @@ namespace Timeline.Tests.Services
public async Task SetNickname_ShouldWork()
{
var username = MockUser.User.Username;
- var user = await _testDatabase.DatabaseContext.Users.Where(u => u.Name == username).Include(u => u.Detail).SingleAsync();
+ var user = await _testDatabase.Context.Users.Where(u => u.Name == username).Include(u => u.Detail).SingleAsync();
var nickname1 = "nickname1";
var nickname2 = "nickname2";
diff --git a/Timeline.Tests/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj
index 21e887eb..bf7a4fb4 100644
--- a/Timeline.Tests/Timeline.Tests.csproj
+++ b/Timeline.Tests/Timeline.Tests.csproj
@@ -15,8 +15,8 @@
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.6">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
@@ -31,8 +31,4 @@
<ItemGroup>
<ProjectReference Include="..\Timeline\Timeline.csproj" />
</ItemGroup>
-
- <ItemGroup>
- <Folder Include="Properties\" />
- </ItemGroup>
</Project>
diff --git a/Timeline.Tests/UsernameValidatorUnitTest.cs b/Timeline.Tests/UsernameValidatorUnitTest.cs
index d02367be..e0f4633f 100644
--- a/Timeline.Tests/UsernameValidatorUnitTest.cs
+++ b/Timeline.Tests/UsernameValidatorUnitTest.cs
@@ -1,5 +1,6 @@
using FluentAssertions;
using Timeline.Models.Validation;
+using Timeline.Tests.Helpers;
using Xunit;
namespace Timeline.Tests