aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Timeline.Tests/Helpers/TestApplication.cs12
-rw-r--r--Timeline.Tests/IntegratedTests/IntegratedTestBase.cs2
-rw-r--r--Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs28
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTest.cs2
-rw-r--r--Timeline.Tests/IntegratedTests/UserTest.cs8
-rw-r--r--Timeline.Tests/UsernameValidatorUnitTest.cs8
-rw-r--r--Timeline/Controllers/ControllerAuthExtensions.cs2
-rw-r--r--Timeline/Controllers/UserAvatarController.cs2
-rw-r--r--Timeline/Controllers/UserController.cs18
-rw-r--r--Timeline/Entities/UserEntity.cs2
-rw-r--r--Timeline/Models/Http/UserInfo.cs21
-rw-r--r--Timeline/Models/Validation/NicknameValidator.cs2
-rw-r--r--Timeline/Services/TimelineService.cs2
-rw-r--r--Timeline/Services/UserService.cs2
14 files changed, 64 insertions, 47 deletions
diff --git a/Timeline.Tests/Helpers/TestApplication.cs b/Timeline.Tests/Helpers/TestApplication.cs
index 14cafea3..bc5deeec 100644
--- a/Timeline.Tests/Helpers/TestApplication.cs
+++ b/Timeline.Tests/Helpers/TestApplication.cs
@@ -17,10 +17,20 @@ namespace Timeline.Tests.Helpers
public TestApplication(WebApplicationFactory<Startup> factory)
{
DatabaseConnection = new SqliteConnection("Data Source=:memory:;");
+ DatabaseConnection.Open();
+
+ var options = new DbContextOptionsBuilder<DevelopmentDatabaseContext>()
+ .UseSqlite(DatabaseConnection)
+ .Options;
+
+ using (var context = new DevelopmentDatabaseContext(options))
+ {
+ context.Database.EnsureCreated();
+ }
Factory = factory.WithWebHostBuilder(builder =>
{
- builder.ConfigureTestServices(services =>
+ builder.ConfigureServices(services =>
{
services.AddDbContext<DatabaseContext, DevelopmentDatabaseContext>(options =>
{
diff --git a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs
index af3e0c2f..242e96cd 100644
--- a/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs
+++ b/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs
@@ -63,7 +63,7 @@ namespace Timeline.Tests.IntegratedTests
foreach (var user in users)
{
- userService.CreateUser(user);
+ userService.CreateUser(user).Wait();
userInfoList.Add(mapper.Map<UserInfo>(user));
userInfoForAdminList.Add(mapper.Map<UserInfoForAdmin>(user));
}
diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs
index 5c472e52..d787d87d 100644
--- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs
+++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs
@@ -103,12 +103,12 @@ namespace Timeline.Tests.IntegratedTests
}
await AssertMembers(new List<UserInfo> { UserInfoList[2] });
{
- var res = await client.DeleteAsync("/users/users1/timeline/members/users2");
+ var res = await client.DeleteAsync("/users/user1/timeline/members/user2");
res.Should().BeDelete(true);
}
await AssertEmptyMembers();
{
- var res = await client.DeleteAsync("/users/users1/timeline/members/users2");
+ var res = await client.DeleteAsync("/users/user1/timeline/members/users2");
res.Should().BeDelete(false);
}
await AssertEmptyMembers();
@@ -137,22 +137,22 @@ namespace Timeline.Tests.IntegratedTests
}
{
- var res = await client.PutAsync("users/user1/timeline/member/user2", null);
+ var res = await client.PutAsync("users/user1/timeline/members/user2", null);
res.Should().HaveStatusCode(opMemberUser);
}
{
- var res = await client.DeleteAsync("users/user1/timeline/member/user2");
+ var res = await client.DeleteAsync("users/user1/timeline/members/user2");
res.Should().HaveStatusCode(opMemberUser);
}
{
- var res = await client.PutAsync("users/admin/timeline/member/user2", null);
+ var res = await client.PutAsync("users/admin/timeline/members/user2", null);
res.Should().HaveStatusCode(opMemberAdmin);
}
{
- var res = await client.DeleteAsync("users/admin/timeline/member/user2");
+ var res = await client.DeleteAsync("users/admin/timeline/members/user2");
res.Should().HaveStatusCode(opMemberAdmin);
}
}
@@ -244,7 +244,7 @@ namespace Timeline.Tests.IntegratedTests
{
using (var client = await CreateClientAsUser())
{
- var res = await client.PutAsync("users/user/timeline/members/user2", null);
+ var res = await client.PutAsync("users/user1/timeline/members/user2", null);
res.Should().HaveStatusCode(200);
}
@@ -283,7 +283,7 @@ namespace Timeline.Tests.IntegratedTests
using (var client = await CreateClientAs(2))
{
{ // post as member
- var res = await client.PostAsJsonAsync("users/user1/timeline/postop/create",
+ var res = await client.PostAsJsonAsync("users/user1/timeline/posts",
new TimelinePostCreateRequest { Content = "aaa" });
res.Should().HaveStatusCode(200);
}
@@ -296,7 +296,7 @@ namespace Timeline.Tests.IntegratedTests
async Task<long> CreatePost(int userNumber)
{
using var client = await CreateClientAs(userNumber);
- var res = await client.PostAsJsonAsync($"users/user1/timeline/postop/create",
+ var res = await client.PostAsJsonAsync($"users/user1/timeline/posts",
new TimelinePostCreateRequest { Content = "aaa" });
return res.Should().HaveStatusCode(200)
.And.HaveJsonBody<TimelinePostInfo>()
@@ -383,7 +383,7 @@ namespace Timeline.Tests.IntegratedTests
.Which;
body.Should().NotBeNull();
body.Content.Should().Be(mockContent);
- body.Author.Should().Be(UserInfoList[1]);
+ body.Author.Should().BeEquivalentTo(UserInfoList[1]);
createRes = body;
}
{
@@ -402,9 +402,9 @@ namespace Timeline.Tests.IntegratedTests
.And.HaveJsonBody<TimelinePostInfo>()
.Which;
body.Should().NotBeNull();
- body.Content.Should().Be(mockContent);
- body.Author.Should().Be(UserInfoList[1]);
- body.Time.Should().Be(mockTime2);
+ body.Content.Should().Be(mockContent2);
+ body.Author.Should().BeEquivalentTo(UserInfoList[1]);
+ body.Time.Should().BeCloseTo(mockTime2, 1000);
createRes2 = body;
}
{
@@ -450,7 +450,7 @@ namespace Timeline.Tests.IntegratedTests
var id2 = await CreatePost(now);
{
- var res = await client.GetAsync("users/user/timeline/posts");
+ var res = await client.GetAsync("users/user1/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/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
index 989207e2..67c2dd9a 100644
--- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
@@ -49,7 +49,7 @@ namespace Timeline.Tests.IntegratedTests
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")
+ async Task GetReturnDefault(string username = "user1")
{
var res = await client.GetAsync($"users/{username}/avatar");
res.Should().HaveStatusCode(200);
diff --git a/Timeline.Tests/IntegratedTests/UserTest.cs b/Timeline.Tests/IntegratedTests/UserTest.cs
index 4c2ccf7a..1b9733ff 100644
--- a/Timeline.Tests/IntegratedTests/UserTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserTest.cs
@@ -44,7 +44,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await CreateClientAsAdministrator();
var res = await client.GetAsync("/users");
res.Should().HaveStatusCode(200)
- .And.HaveJsonBody<UserInfo[]>()
+ .And.HaveJsonBody<UserInfoForAdmin[]>()
.Which.Should().BeEquivalentTo(UserInfoForAdminList);
}
@@ -74,7 +74,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await CreateClientAsAdministrator();
var res = await client.GetAsync($"/users/user1");
res.Should().HaveStatusCode(200)
- .And.HaveJsonBody<UserInfo>()
+ .And.HaveJsonBody<UserInfoForAdmin>()
.Which.Should().BeEquivalentTo(UserInfoForAdminList[1]);
}
@@ -142,7 +142,7 @@ namespace Timeline.Tests.IntegratedTests
{
// Token should expire.
- var res = await userClient.GetAsync("/users");
+ var res = await userClient.GetAsync("/testing/auth/Authorize");
res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
}
@@ -198,7 +198,7 @@ namespace Timeline.Tests.IntegratedTests
[Fact]
public async Task Patch_NoAuth_Unauthorized()
{
- using var client = await CreateClientAsUser();
+ using var client = await CreateDefaultClient();
var res = await client.PatchAsJsonAsync("/users/user1", new UserPatchRequest { Nickname = "aaa" });
res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
}
diff --git a/Timeline.Tests/UsernameValidatorUnitTest.cs b/Timeline.Tests/UsernameValidatorUnitTest.cs
index 1a09d477..0f844452 100644
--- a/Timeline.Tests/UsernameValidatorUnitTest.cs
+++ b/Timeline.Tests/UsernameValidatorUnitTest.cs
@@ -22,12 +22,6 @@ namespace Timeline.Tests
}
[Fact]
- public void Null()
- {
- FailAndMessage(null).Should().ContainEquivalentOf("null");
- }
-
- [Fact]
public void NotString()
{
var (result, message) = _validator.Validate(123);
@@ -58,6 +52,7 @@ namespace Timeline.Tests
}
[Theory]
+ [InlineData(null)]
[InlineData("abc")]
[InlineData("-abc")]
[InlineData("_abc")]
@@ -68,7 +63,6 @@ namespace Timeline.Tests
[InlineData("a-b_c")]
public void Success(string value)
{
-
var (result, _) = _validator.Validate(value);
result.Should().BeTrue();
}
diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs
index 90da8a93..34fd4d99 100644
--- a/Timeline/Controllers/ControllerAuthExtensions.cs
+++ b/Timeline/Controllers/ControllerAuthExtensions.cs
@@ -34,7 +34,7 @@ namespace Timeline.Controllers
var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier);
if (claim == null)
- throw new InvalidOperationException("Failed to get user id because User has no NameIdentifier claim.");
+ return null;
if (long.TryParse(claim.Value, out var value))
return value;
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs
index ab0ad8e7..2dd279a8 100644
--- a/Timeline/Controllers/UserAvatarController.cs
+++ b/Timeline/Controllers/UserAvatarController.cs
@@ -78,7 +78,7 @@ namespace Timeline.Controllers
[HttpPut("users/{username}/avatar")]
[Authorize]
- [RequireContentLength]
+ [RequireContentType, RequireContentLength]
[Consumes("image/png", "image/jpeg", "image/gif", "image/webp")]
public async Task<IActionResult> Put([FromRoute][Username] string username)
{
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index 400a518c..fa73c6f9 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -42,7 +42,13 @@ namespace Timeline.Controllers
{
var users = await _userService.GetUsers();
var administrator = this.IsAdministrator();
- return Ok(users.Select(u => ConvertToUserInfo(u, administrator)).ToArray());
+ // Note: the (object) explicit conversion. If not convert,
+ // then result is a IUserInfo array and JsonSerializer will
+ // treat all element as IUserInfo and deserialize only properties
+ // in IUserInfo. So we convert it to object to make an object
+ // array so that JsonSerializer use the runtime type.
+ var result = users.Select(u => (object)ConvertToUserInfo(u, administrator)).ToArray();
+ return Ok(result);
}
[HttpGet("users/{username}")]
@@ -106,15 +112,11 @@ namespace Timeline.Controllers
[HttpDelete("users/{username}"), AdminAuthorize]
public async Task<ActionResult<CommonDeleteResponse>> Delete([FromRoute][Username] string username)
{
- try
- {
- await _userService.DeleteUser(username);
+ var delete = await _userService.DeleteUser(username);
+ if (delete)
return Ok(CommonDeleteResponse.Delete());
- }
- catch (UserNotExistException)
- {
+ else
return Ok(CommonDeleteResponse.NotExist());
- }
}
[HttpPost("userop/createuser"), AdminAuthorize]
diff --git a/Timeline/Entities/UserEntity.cs b/Timeline/Entities/UserEntity.cs
index dae6979f..946c3fa2 100644
--- a/Timeline/Entities/UserEntity.cs
+++ b/Timeline/Entities/UserEntity.cs
@@ -29,7 +29,7 @@ namespace Timeline.Entities
[Column("version"), Required]
public long Version { get; set; }
- [Column("nickname"), MaxLength(40)]
+ [Column("nickname"), MaxLength(100)]
public string? Nickname { get; set; }
public UserAvatarEntity? Avatar { get; set; }
diff --git a/Timeline/Models/Http/UserInfo.cs b/Timeline/Models/Http/UserInfo.cs
index 6029b8aa..62d989a2 100644
--- a/Timeline/Models/Http/UserInfo.cs
+++ b/Timeline/Models/Http/UserInfo.cs
@@ -29,21 +29,30 @@ namespace Timeline.Models.Http
public bool Administrator { get; set; }
}
- public class UserInfoSetAvatarUrlAction : IMappingAction<object, IUserInfo>
+ public class UserInfoAvatarUrlValueResolver : IValueResolver<User, IUserInfo, string>
{
private readonly IActionContextAccessor _actionContextAccessor;
private readonly IUrlHelperFactory _urlHelperFactory;
- public UserInfoSetAvatarUrlAction(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory)
+ public UserInfoAvatarUrlValueResolver()
+ {
+ }
+
+ public UserInfoAvatarUrlValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory)
{
_actionContextAccessor = actionContextAccessor;
_urlHelperFactory = urlHelperFactory;
}
- public void Process(object source, IUserInfo destination, ResolutionContext context)
+ public string Resolve(User source, IUserInfo destination, string destMember, ResolutionContext context)
{
+ if (_actionContextAccessor == null)
+ {
+ return $"/users/{destination.Username}/avatar";
+ }
+
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
- destination.AvatarUrl = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController), new { destination.Username });
+ return urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController), new { destination.Username });
}
}
@@ -51,8 +60,8 @@ namespace Timeline.Models.Http
{
public UserInfoAutoMapperProfile()
{
- CreateMap<User, UserInfo>().AfterMap<UserInfoSetAvatarUrlAction>();
- CreateMap<User, UserInfoForAdmin>().AfterMap<UserInfoSetAvatarUrlAction>();
+ CreateMap<User, UserInfo>().ForMember(u => u.AvatarUrl, opt => opt.MapFrom<UserInfoAvatarUrlValueResolver>());
+ CreateMap<User, UserInfoForAdmin>().ForMember(u => u.AvatarUrl, opt => opt.MapFrom<UserInfoAvatarUrlValueResolver>());
}
}
}
diff --git a/Timeline/Models/Validation/NicknameValidator.cs b/Timeline/Models/Validation/NicknameValidator.cs
index 53a2916b..1d6ab163 100644
--- a/Timeline/Models/Validation/NicknameValidator.cs
+++ b/Timeline/Models/Validation/NicknameValidator.cs
@@ -7,7 +7,7 @@ namespace Timeline.Models.Validation
{
protected override (bool, string) DoValidate(string value)
{
- if (value.Length > 10)
+ if (value.Length > 25)
return (false, MessageTooLong);
return (true, GetSuccessMessage());
diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs
index 89936aa2..16402f3e 100644
--- a/Timeline/Services/TimelineService.cs
+++ b/Timeline/Services/TimelineService.cs
@@ -295,7 +295,7 @@ namespace Timeline.Services
{
if (entity.Content != null) // otherwise it is deleted
{
- var author = Mapper.Map<UserInfo>(UserService.GetUserById(entity.AuthorId));
+ var author = Mapper.Map<UserInfo>(await UserService.GetUserById(entity.AuthorId));
posts.Add(new TimelinePostInfo
{
Id = entity.Id,
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index 1197bb73..d2dc969e 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -300,12 +300,14 @@ namespace Timeline.Services
var administrator = info.Administrator ?? false;
var password = info.Password;
+ var nickname = info.Nickname;
var newEntity = new UserEntity
{
Username = username,
Password = _passwordService.HashPassword(password),
Roles = UserRoleConvert.ToString(administrator),
+ Nickname = nickname,
Version = 1
};
_databaseContext.Users.Add(newEntity);