From 62a3557ab62e1fa188e7498643d7cf0221a18322 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Wed, 21 Aug 2019 18:33:07 +0800 Subject: Add database entity and service. --- Timeline.Tests/UserDetailServiceTest.cs | 180 ++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Timeline.Tests/UserDetailServiceTest.cs (limited to 'Timeline.Tests/UserDetailServiceTest.cs') diff --git a/Timeline.Tests/UserDetailServiceTest.cs b/Timeline.Tests/UserDetailServiceTest.cs new file mode 100644 index 00000000..fa8df3ae --- /dev/null +++ b/Timeline.Tests/UserDetailServiceTest.cs @@ -0,0 +1,180 @@ +using FluentAssertions; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Models; +using Timeline.Services; +using Timeline.Tests.Helpers; +using Timeline.Tests.Mock.Data; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests +{ + public class UserDetailServiceTest : IDisposable + { + private readonly LoggerFactory _loggerFactory; + private readonly TestDatabase _database; + + private readonly UserDetailService _service; + + public UserDetailServiceTest(ITestOutputHelper outputHelper) + { + _loggerFactory = MyTestLoggerFactory.Create(outputHelper); + _database = new TestDatabase(); + + _service = new UserDetailService(_loggerFactory.CreateLogger(), _database.DatabaseContext); + } + + public void Dispose() + { + _loggerFactory.Dispose(); + _database.Dispose(); + } + + [Fact] + public void GetDetail_ShouldThrow_ArgumentException() + { + // no need to await because arguments are checked syncronizedly. + _service.Invoking(s => s.GetUserDetail(null)).Should().Throw() + .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); + _service.Invoking(s => s.GetUserDetail("")).Should().Throw() + .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public void GetDetail_ShouldThrow_UserNotExistException() + { + const string username = "usernotexist"; + _service.Awaiting(s => s.GetUserDetail(username)).Should().Throw() + .Where(e => e.Username == username); + } + + [Fact] + public async Task GetDetail_Should_Create_And_ReturnDefault() + { + { + var detail = await _service.GetUserDetail(MockUsers.UserUsername); + detail.Should().BeEquivalentTo(new UserDetail()); + } + + { + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + var detail = context.UserDetails.Where(e => e.UserId == userId).Single(); + detail.QQ.Should().BeNullOrEmpty(); + detail.EMail.Should().BeNullOrEmpty(); + detail.PhoneNumber.Should().BeNullOrEmpty(); + detail.Description.Should().BeNullOrEmpty(); + } + } + + [Fact] + public async Task GetDetail_Should_ReturnData() + { + const string email = "ha@aaa.net"; + const string description = "hahaha"; + + var context = _database.DatabaseContext; + UserDetailEntity entity; + + { + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + entity = new UserDetailEntity + { + EMail = email, + Description = description, + UserId = userId + }; + context.Add(entity); + await context.SaveChangesAsync(); + } + + { + var detail = await _service.GetUserDetail(MockUsers.UserUsername); + detail.Should().BeEquivalentTo(new UserDetail + { + EMail = email, + Description = description + }); + } + } + + [Fact] + public void UpdateDetail_ShouldThrow_ArgumentException() + { + // no need to await because arguments are checked syncronizedly. + _service.Invoking(s => s.UpdateUserDetail(null, new UserDetail())).Should().Throw() + .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); + _service.Invoking(s => s.UpdateUserDetail("", new UserDetail())).Should().Throw() + .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); + _service.Invoking(s => s.UpdateUserDetail("aaa", null)).Should().Throw() + .Where(e => e.ParamName == "detail"); + } + + [Fact] + public void UpdateDetail_ShouldThrow_UserNotExistException() + { + const string username = "usernotexist"; + _service.Awaiting(s => s.UpdateUserDetail(username, new UserDetail())).Should().Throw() + .Where(e => e.Username == username); + } + + [Fact] + public async Task UpdateDetail_Should_Work() + { + UserDetailEntity entity; + + await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail()); + + { + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + entity = context.UserDetails.Where(e => e.UserId == userId).Single(); + entity.QQ.Should().BeNullOrEmpty(); + entity.EMail.Should().BeNullOrEmpty(); + entity.PhoneNumber.Should().BeNullOrEmpty(); + entity.Description.Should().BeNullOrEmpty(); + } + + const string email = "ha@aaa.net"; + const string phoneNumber = "12345678910"; + const string description = "hahaha"; + + await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail + { + EMail = email, + PhoneNumber = phoneNumber, + Description = description + }); + + { + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + entity = context.UserDetails.Where(e => e.UserId == userId).Single(); + entity.QQ.Should().BeNullOrEmpty(); + entity.EMail.Should().Be(email); + entity.PhoneNumber.Should().Be(phoneNumber); + entity.Description.Should().Be(description); + } + + const string newDescription = "new description"; + + await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail + { + EMail = null, + PhoneNumber = "", + Description = newDescription + }); + + { + entity.QQ.Should().BeNullOrEmpty(); + entity.EMail.Should().Be(email); + entity.PhoneNumber.Should().BeNullOrEmpty(); + entity.Description.Should().Be(newDescription); + } + } + } +} -- cgit v1.2.3 From c244406dec5686a132cc0133bd02230af8ac605e Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Wed, 21 Aug 2019 22:31:21 +0800 Subject: Improve unit tests. --- Timeline.Tests/UserDetailServiceTest.cs | 105 +++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 37 deletions(-) (limited to 'Timeline.Tests/UserDetailServiceTest.cs') diff --git a/Timeline.Tests/UserDetailServiceTest.cs b/Timeline.Tests/UserDetailServiceTest.cs index fa8df3ae..292b528b 100644 --- a/Timeline.Tests/UserDetailServiceTest.cs +++ b/Timeline.Tests/UserDetailServiceTest.cs @@ -123,58 +123,89 @@ namespace Timeline.Tests } [Fact] - public async Task UpdateDetail_Should_Work() + public async Task UpdateDetail_Empty_Should_Work() { - UserDetailEntity entity; - await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail()); + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + var entity = context.UserDetails.Where(e => e.UserId == userId).Single(); + entity.QQ.Should().BeNullOrEmpty(); + entity.EMail.Should().BeNullOrEmpty(); + entity.PhoneNumber.Should().BeNullOrEmpty(); + entity.Description.Should().BeNullOrEmpty(); + } + + [Theory] + [InlineData(nameof(UserDetail.QQ), nameof(UserDetailEntity.QQ), "12345678910", "987654321")] + [InlineData(nameof(UserDetail.EMail), nameof(UserDetailEntity.EMail), "aaa@aaa.aaa", "bbb@bbb.bbb")] + [InlineData(nameof(UserDetail.PhoneNumber), nameof(UserDetailEntity.PhoneNumber), "12345678910", "987654321")] + [InlineData(nameof(UserDetail.Description), nameof(UserDetailEntity.Description), "descriptionA", "descriptionB")] + public async Task UpdateDetail_Single_Should_Work(string propertyName, string entityPropertyName, string mockData1, string mockData2) + { + + UserDetail CreateWith(string propertyValue) { - var context = _database.DatabaseContext; - var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); - entity = context.UserDetails.Where(e => e.UserId == userId).Single(); - entity.QQ.Should().BeNullOrEmpty(); - entity.EMail.Should().BeNullOrEmpty(); - entity.PhoneNumber.Should().BeNullOrEmpty(); - entity.Description.Should().BeNullOrEmpty(); + var detail = new UserDetail(); + typeof(UserDetail).GetProperty(propertyName).SetValue(detail, propertyValue); + return detail; } - const string email = "ha@aaa.net"; - const string phoneNumber = "12345678910"; - const string description = "hahaha"; + await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(mockData1)); - await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail - { - EMail = email, - PhoneNumber = phoneNumber, - Description = description - }); + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + var entity = context.UserDetails.Where(e => e.UserId == userId).Single(); + void TestWith(string propertyValue) { - var context = _database.DatabaseContext; - var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); - entity = context.UserDetails.Where(e => e.UserId == userId).Single(); - entity.QQ.Should().BeNullOrEmpty(); - entity.EMail.Should().Be(email); - entity.PhoneNumber.Should().Be(phoneNumber); - entity.Description.Should().Be(description); + typeof(UserDetailEntity).GetProperty(entityPropertyName).GetValue(entity).Should().Equals(propertyValue); + foreach (var p in typeof(UserDetailEntity).GetProperties().Where(p => p.Name != entityPropertyName)) + (p.GetValue(entity) as string).Should().BeNullOrEmpty(); } - const string newDescription = "new description"; + TestWith(mockData1); - await _service.UpdateUserDetail(MockUsers.UserUsername, new UserDetail + await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith(mockData2)); + TestWith(mockData2); + await _service.UpdateUserDetail(MockUsers.UserUsername, CreateWith("")); + TestWith(""); + } + + [Fact] + public async Task UpdateDetail_Multiple_Should_Work() + { + var detail = new UserDetail { - EMail = null, - PhoneNumber = "", - Description = newDescription - }); + QQ = "12345678", + EMail = "aaa@aaa.aaa", + PhoneNumber = "11111111111", + Description = "aaaaaaaaaa" + }; + + await _service.UpdateUserDetail(MockUsers.UserUsername, detail); + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + var entity = context.UserDetails.Where(e => e.UserId == userId).Single(); + entity.QQ.Should().Equals(detail.QQ); + entity.EMail.Should().Equals(detail.EMail); + entity.PhoneNumber.Should().Equals(detail.PhoneNumber); + entity.Description.Should().Equals(detail.Description); + + var detail2 = new UserDetail { - entity.QQ.Should().BeNullOrEmpty(); - entity.EMail.Should().Be(email); - entity.PhoneNumber.Should().BeNullOrEmpty(); - entity.Description.Should().Be(newDescription); - } + QQ = null, + EMail = "bbb@bbb.bbb", + PhoneNumber = "", + Description = "bbbbbbbbb" + }; + + await _service.UpdateUserDetail(MockUsers.UserUsername, detail2); + entity.QQ.Should().Equals(detail.QQ); + entity.EMail.Should().Equals(detail2.EMail); + entity.PhoneNumber.Should().BeNullOrEmpty(); + entity.Description.Should().Equals(detail2.Description); } } } -- cgit v1.2.3 From 9112f288f638f34aab48fdb965ff9703a99adb4d Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 22 Aug 2019 14:54:47 +0800 Subject: Add nickname. Step 1. --- Timeline.Tests/UserDetailServiceTest.cs | 70 +++++++++++++++++++++++++++++++-- Timeline/Entities/UserDetail.cs | 3 ++ Timeline/Models/UserDetail.cs | 7 +++- Timeline/Services/UserDetailService.cs | 48 ++++++++++++++++++---- 4 files changed, 117 insertions(+), 11 deletions(-) (limited to 'Timeline.Tests/UserDetailServiceTest.cs') diff --git a/Timeline.Tests/UserDetailServiceTest.cs b/Timeline.Tests/UserDetailServiceTest.cs index 292b528b..f9170c42 100644 --- a/Timeline.Tests/UserDetailServiceTest.cs +++ b/Timeline.Tests/UserDetailServiceTest.cs @@ -34,6 +34,68 @@ namespace Timeline.Tests _database.Dispose(); } + [Fact] + public void GetNickname_ShouldThrow_ArgumentException() + { + // no need to await because arguments are checked syncronizedly. + _service.Invoking(s => s.GetUserNickname(null)).Should().Throw() + .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); + _service.Invoking(s => s.GetUserNickname("")).Should().Throw() + .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public void GetNickname_ShouldThrow_UserNotExistException() + { + const string username = "usernotexist"; + _service.Awaiting(s => s.GetUserNickname(username)).Should().Throw() + .Where(e => e.Username == username); + } + + [Fact] + public async Task GetNickname_Should_Create_And_ReturnDefault() + { + { + var nickname = await _service.GetUserNickname(MockUsers.UserUsername); + nickname.Should().BeNull(); + } + + { + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + var detail = context.UserDetails.Where(e => e.UserId == userId).Single(); + detail.Nickname.Should().BeNullOrEmpty(); + detail.QQ.Should().BeNullOrEmpty(); + detail.EMail.Should().BeNullOrEmpty(); + detail.PhoneNumber.Should().BeNullOrEmpty(); + detail.Description.Should().BeNullOrEmpty(); + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("nickname")] + public async Task GetNickname_Should_ReturnData(string nickname) + { + { + var context = _database.DatabaseContext; + var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); + var entity = new UserDetailEntity + { + Nickname = nickname, + UserId = userId + }; + context.Add(entity); + await context.SaveChangesAsync(); + } + + { + var n = await _service.GetUserNickname(MockUsers.UserUsername); + n.Should().Equals(string.IsNullOrEmpty(nickname) ? null : nickname); + } + } + [Fact] public void GetDetail_ShouldThrow_ArgumentException() { @@ -64,6 +126,7 @@ namespace Timeline.Tests var context = _database.DatabaseContext; var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); var detail = context.UserDetails.Where(e => e.UserId == userId).Single(); + detail.Nickname.Should().BeNullOrEmpty(); detail.QQ.Should().BeNullOrEmpty(); detail.EMail.Should().BeNullOrEmpty(); detail.PhoneNumber.Should().BeNullOrEmpty(); @@ -77,12 +140,11 @@ namespace Timeline.Tests const string email = "ha@aaa.net"; const string description = "hahaha"; - var context = _database.DatabaseContext; - UserDetailEntity entity; { + var context = _database.DatabaseContext; var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); - entity = new UserDetailEntity + var entity = new UserDetailEntity { EMail = email, Description = description, @@ -130,6 +192,7 @@ namespace Timeline.Tests var context = _database.DatabaseContext; var userId = await DatabaseExtensions.CheckAndGetUser(context.Users, MockUsers.UserUsername); var entity = context.UserDetails.Where(e => e.UserId == userId).Single(); + entity.Nickname.Should().BeNullOrEmpty(); entity.QQ.Should().BeNullOrEmpty(); entity.EMail.Should().BeNullOrEmpty(); entity.PhoneNumber.Should().BeNullOrEmpty(); @@ -137,6 +200,7 @@ namespace Timeline.Tests } [Theory] + [InlineData(nameof(UserDetail.Nickname), nameof(UserDetailEntity.Nickname), "aaaa", "bbbb")] [InlineData(nameof(UserDetail.QQ), nameof(UserDetailEntity.QQ), "12345678910", "987654321")] [InlineData(nameof(UserDetail.EMail), nameof(UserDetailEntity.EMail), "aaa@aaa.aaa", "bbb@bbb.bbb")] [InlineData(nameof(UserDetail.PhoneNumber), nameof(UserDetailEntity.PhoneNumber), "12345678910", "987654321")] diff --git a/Timeline/Entities/UserDetail.cs b/Timeline/Entities/UserDetail.cs index 9bc6f5e5..6e582234 100644 --- a/Timeline/Entities/UserDetail.cs +++ b/Timeline/Entities/UserDetail.cs @@ -9,6 +9,9 @@ namespace Timeline.Entities [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } + [Column("nickname"), MaxLength(15)] + public string Nickname { get; set; } + [Column("qq"), MaxLength(15)] public string QQ { get; set; } diff --git a/Timeline/Models/UserDetail.cs b/Timeline/Models/UserDetail.cs index 4af88450..86866d8b 100644 --- a/Timeline/Models/UserDetail.cs +++ b/Timeline/Models/UserDetail.cs @@ -1,10 +1,14 @@ -using Timeline.Entities; +using System.ComponentModel.DataAnnotations; +using Timeline.Entities; using Timeline.Models.Validation; namespace Timeline.Models { public class UserDetail { + [MaxLength(10)] + public string Nickname { get; set; } + [ValidateWith(typeof(UserDetailValidators.QQValidator))] public string QQ { get; set; } @@ -28,6 +32,7 @@ namespace Timeline.Models { return new UserDetail { + Nickname = CoerceEmptyToNull(entity.Nickname), QQ = CoerceEmptyToNull(entity.QQ), EMail = CoerceEmptyToNull(entity.EMail), PhoneNumber = CoerceEmptyToNull(entity.PhoneNumber), diff --git a/Timeline/Services/UserDetailService.cs b/Timeline/Services/UserDetailService.cs index 0bb745f3..a8ed662b 100644 --- a/Timeline/Services/UserDetailService.cs +++ b/Timeline/Services/UserDetailService.cs @@ -11,6 +11,15 @@ namespace Timeline.Services { public interface IUserDetailService { + /// + /// Get the nickname of user. + /// + /// The username to get nickname of. + /// The user's nickname. Null if not set. + /// Thrown if is null or empty. + /// Thrown if user doesn't exist. + Task GetUserNickname(string username); + /// /// Get the detail of user. /// @@ -42,23 +51,45 @@ namespace Timeline.Services _databaseContext = databaseContext; } + private async Task CreateEntity(long userId) + { + var entity = new UserDetailEntity() + { + UserId = userId + }; + _databaseContext.UserDetails.Add(entity); + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation("An entity is created in user_details."); + return entity; + } + // Check the existence of user detail entry private async Task CheckAndInit(long userId) { var detail = await _databaseContext.UserDetails.Where(e => e.UserId == userId).SingleOrDefaultAsync(); if (detail == null) { - detail = new UserDetailEntity() - { - UserId = userId - }; - _databaseContext.UserDetails.Add(detail); - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation("An entity is created in user_details."); + detail = await CreateEntity(userId); } return detail; } + public async Task GetUserNickname(string username) + { + var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username); + var detail = await _databaseContext.UserDetails.Where(e => e.UserId == userId).Select(e => new { e.Nickname }).SingleOrDefaultAsync(); + if (detail == null) + { + var entity = await CreateEntity(userId); + return null; + } + else + { + var nickname = detail.Nickname; + return string.IsNullOrEmpty(nickname) ? null : nickname; + } + } + public async Task GetUserDetail(string username) { var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username); @@ -74,6 +105,9 @@ namespace Timeline.Services var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username); var detailEntity = await CheckAndInit(userId); + if (detail.Nickname != null) + detailEntity.Nickname = detail.Nickname; + if (detail.QQ != null) detailEntity.QQ = detail.QQ; -- cgit v1.2.3