From 280faac75b113d9a4bbecd7e4ea3499f1691ce61 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 24 Oct 2019 19:57:59 +0800 Subject: ... --- Timeline.Tests/DatabaseTest.cs | 14 +- Timeline.Tests/GlobalSuppressions.cs | 2 +- .../Helpers/AsyncFunctionAssertionsExtensions.cs | 16 ++ Timeline.Tests/IntegratedTests/UserAvatarTest.cs | 20 ++ Timeline.Tests/Mock/Data/TestUsers.cs | 2 +- Timeline.Tests/UserAvatarServiceTest.cs | 206 +++++++++------------ Timeline/Entities/UserAvatar.cs | 12 -- .../Services/UserAvatarService.Designer.cs | 6 +- Timeline/Resources/Services/UserAvatarService.resx | 4 +- Timeline/Services/ETagGenerator.cs | 7 +- Timeline/Services/UserAvatarService.cs | 19 +- 11 files changed, 162 insertions(+), 146 deletions(-) create mode 100644 Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs diff --git a/Timeline.Tests/DatabaseTest.cs b/Timeline.Tests/DatabaseTest.cs index c45c0f66..b5681491 100644 --- a/Timeline.Tests/DatabaseTest.cs +++ b/Timeline.Tests/DatabaseTest.cs @@ -26,11 +26,21 @@ namespace Timeline.Tests [Fact] public void DeleteUserShouldAlsoDeleteAvatar() { - _context.UserAvatars.Count().Should().Be(2); var user = _context.Users.First(); - _context.Users.Remove(user); + _context.UserAvatars.Count().Should().Be(0); + _context.UserAvatars.Add(new UserAvatar + { + Data = null, + Type = null, + ETag = null, + LastModified = DateTime.Now, + UserId = user.Id + }); _context.SaveChanges(); _context.UserAvatars.Count().Should().Be(1); + _context.Users.Remove(user); + _context.SaveChanges(); + _context.UserAvatars.Count().Should().Be(0); } } } diff --git a/Timeline.Tests/GlobalSuppressions.cs b/Timeline.Tests/GlobalSuppressions.cs index 2191a5c4..1d1d294b 100644 --- a/Timeline.Tests/GlobalSuppressions.cs +++ b/Timeline.Tests/GlobalSuppressions.cs @@ -5,10 +5,10 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "This is not a UI application.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Tests name have underscores.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Test may catch all exceptions.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Test classes can be nested.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "This is redundant.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1063:Implement IDisposable Correctly", Justification = "Test classes do not need to implement it that way.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Test classes do not need to implement it that way.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2234:Pass system uri objects instead of strings", Justification = "I really don't understand this rule.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Tests do not need make strings resources.")] - diff --git a/Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs b/Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs new file mode 100644 index 00000000..b78309c0 --- /dev/null +++ b/Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs @@ -0,0 +1,16 @@ +using FluentAssertions; +using FluentAssertions.Primitives; +using FluentAssertions.Specialized; +using System; +using System.Threading.Tasks; + +namespace Timeline.Tests.Helpers +{ + public static class AsyncFunctionAssertionsExtensions + { + public static async Task> ThrowAsync(this AsyncFunctionAssertions assertions, Type exceptionType, string because = "", params object[] becauseArgs) + { + return (await assertions.ThrowAsync(because, becauseArgs)).Which.Should().BeAssignableTo(exceptionType); + } + } +} diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index ba6d98e1..ce389046 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -40,6 +40,7 @@ namespace Timeline.Tests.IntegratedTests } [Fact] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "HttpMessageRequest should be disposed ???")] public async Task Test() { Avatar mockAvatar = new Avatar @@ -264,6 +265,25 @@ namespace Timeline.Tests.IntegratedTests .And.Should().HaveCommonBody().Which.Code.Should().Be(Delete.UserNotExist); } } + + // bad username check + using (var client = await _factory.CreateClientAsAdmin()) + { + { + var res = await client.GetAsync("users/u!ser/avatar"); + res.Should().BeInvalidModel(); + } + + { + var res = await client.PutByteArrayAsync("users/u!ser/avatar", ImageHelper.CreatePngWithSize(100, 100), "image/png"); + res.Should().BeInvalidModel(); + } + + { + var res = await client.DeleteAsync("users/u!ser/avatar"); + res.Should().BeInvalidModel(); + } + } } } } \ No newline at end of file diff --git a/Timeline.Tests/Mock/Data/TestUsers.cs b/Timeline.Tests/Mock/Data/TestUsers.cs index 6b0a9997..fa75236a 100644 --- a/Timeline.Tests/Mock/Data/TestUsers.cs +++ b/Timeline.Tests/Mock/Data/TestUsers.cs @@ -36,7 +36,7 @@ namespace Timeline.Tests.Mock.Data Name = user.Username, EncryptedPassword = passwordService.HashPassword(user.Password), RoleString = UserRoleConvert.ToString(user.Administrator), - Avatar = UserAvatar.Create(DateTime.Now) + Avatar = null }; } diff --git a/Timeline.Tests/UserAvatarServiceTest.cs b/Timeline.Tests/UserAvatarServiceTest.cs index 7489517b..79fa6f5c 100644 --- a/Timeline.Tests/UserAvatarServiceTest.cs +++ b/Timeline.Tests/UserAvatarServiceTest.cs @@ -2,8 +2,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Moq; using SixLabors.ImageSharp.Formats.Png; using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -11,40 +13,12 @@ using Timeline.Entities; using Timeline.Services; using Timeline.Tests.Helpers; using Timeline.Tests.Mock.Data; +using Timeline.Tests.Mock.Services; using Xunit; using Xunit.Abstractions; namespace Timeline.Tests { - public class MockDefaultUserAvatarProvider : IDefaultUserAvatarProvider - { - public static string ETag { get; } = "Hahaha"; - - public static AvatarInfo AvatarInfo { get; } = new AvatarInfo - { - Avatar = new Avatar { Type = "image/test", Data = Encoding.ASCII.GetBytes("test") }, - LastModified = DateTime.Now - }; - - public Task GetDefaultAvatarETag() - { - return Task.FromResult(ETag); - } - - public Task GetDefaultAvatar() - { - return Task.FromResult(AvatarInfo); - } - } - - public class MockUserAvatarValidator : IUserAvatarValidator - { - public Task Validate(Avatar avatar) - { - return Task.CompletedTask; - } - } - public class UserAvatarValidatorTest : IClassFixture { private readonly UserAvatarValidator _validator; @@ -106,22 +80,30 @@ namespace Timeline.Tests } } - public class UserAvatarServiceTest : IDisposable, IClassFixture, IClassFixture + public class UserAvatarServiceTest : IDisposable { - private UserAvatar MockAvatarEntity1 { get; } = new UserAvatar + private UserAvatar CreateMockAvatarEntity(string key) => new UserAvatar { - Type = "image/testaaa", - Data = Encoding.ASCII.GetBytes("amock"), - ETag = "aaaa", + Type = $"image/test{key}", + Data = Encoding.ASCII.GetBytes($"mock{key}"), + ETag = $"etag{key}", LastModified = DateTime.Now }; - private UserAvatar MockAvatarEntity2 { get; } = new UserAvatar + private AvatarInfo CreateMockAvatarInfo(string key) => new AvatarInfo { - Type = "image/testbbb", - Data = Encoding.ASCII.GetBytes("bmock"), - ETag = "bbbb", - LastModified = DateTime.Now + TimeSpan.FromMinutes(1) + Avatar = new Avatar + { + Type = $"image/test{key}", + Data = Encoding.ASCII.GetBytes($"mock{key}") + }, + LastModified = DateTime.Now + }; + + private Avatar CreateMockAvatar(string key) => new Avatar + { + Type = $"image/test{key}", + Data = Encoding.ASCII.GetBytes($"mock{key}") }; private Avatar ToAvatar(UserAvatar entity) @@ -150,137 +132,115 @@ namespace Timeline.Tests to.LastModified = from.LastModified; } - private readonly MockDefaultUserAvatarProvider _mockDefaultUserAvatarProvider; + private readonly Mock _mockDefaultAvatarProvider; + private readonly Mock _mockValidator; + private readonly Mock _mockETagGenerator; + private readonly Mock _mockClock; private readonly TestDatabase _database; - private readonly IETagGenerator _eTagGenerator; - private readonly UserAvatarService _service; - public UserAvatarServiceTest(MockDefaultUserAvatarProvider mockDefaultUserAvatarProvider, MockUserAvatarValidator mockUserAvatarValidator) + public UserAvatarServiceTest() { - _mockDefaultUserAvatarProvider = mockDefaultUserAvatarProvider; + _mockDefaultAvatarProvider = new Mock(); + _mockValidator = new Mock(); + _mockETagGenerator = new Mock(); + _mockClock = new Mock(); _database = new TestDatabase(); - _eTagGenerator = new ETagGenerator(); - - _service = new UserAvatarService(NullLogger.Instance, _database.DatabaseContext, _mockDefaultUserAvatarProvider, mockUserAvatarValidator, _eTagGenerator); + _service = new UserAvatarService(NullLogger.Instance, _database.DatabaseContext, _mockDefaultAvatarProvider.Object, _mockValidator.Object, _mockETagGenerator.Object, _mockClock.Object); } - public void Dispose() { _database.Dispose(); } - [Fact] - public void GetAvatarETag_ShouldThrow_ArgumentException() + [Theory] + [InlineData(null, typeof(ArgumentNullException))] + [InlineData("", typeof(UsernameBadFormatException))] + [InlineData("a!a", typeof(UsernameBadFormatException))] + [InlineData("usernotexist", typeof(UserNotExistException))] + public async Task GetAvatarETag_ShouldThrow(string username, Type exceptionType) { - // no need to await because arguments are checked syncronizedly. - _service.Invoking(s => s.GetAvatarETag(null)).Should().Throw() - .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); - _service.Invoking(s => s.GetAvatarETag("")).Should().Throw() - .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public void GetAvatarETag_ShouldThrow_UserNotExistException() - { - const string username = "usernotexist"; - _service.Awaiting(s => s.GetAvatarETag(username)).Should().Throw() - .Where(e => e.Username == username); + await _service.Awaiting(s => s.GetAvatarETag(username)).Should().ThrowAsync(exceptionType); } [Fact] public async Task GetAvatarETag_ShouldReturn_Default() { - string username = MockUser.User.Username; - (await _service.GetAvatarETag(username)).Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatarETag())); + const string etag = "aaaaaa"; + _mockDefaultAvatarProvider.Setup(p => p.GetDefaultAvatarETag()).ReturnsAsync(etag); + (await _service.GetAvatarETag(MockUser.User.Username)).Should().Be(etag); } [Fact] public async Task GetAvatarETag_ShouldReturn_Data() { string username = MockUser.User.Username; + var mockAvatarEntity = CreateMockAvatarEntity("aaa"); { - // create mock data var context = _database.DatabaseContext; var user = await context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync(); - Set(user.Avatar, MockAvatarEntity1); + user.Avatar = mockAvatarEntity; await context.SaveChangesAsync(); } - - (await _service.GetAvatarETag(username)).Should().BeEquivalentTo(MockAvatarEntity1.ETag); + (await _service.GetAvatarETag(username)).Should().BeEquivalentTo(mockAvatarEntity.ETag); } - [Fact] - public void GetAvatar_ShouldThrow_ArgumentException() + [Theory] + [InlineData(null, typeof(ArgumentNullException))] + [InlineData("", typeof(UsernameBadFormatException))] + [InlineData("a!a", typeof(UsernameBadFormatException))] + [InlineData("usernotexist", typeof(UserNotExistException))] + public async Task GetAvatar_ShouldThrow(string username, Type exceptionType) { - // no need to await because arguments are checked syncronizedly. - _service.Invoking(s => s.GetAvatar(null)).Should().Throw() - .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); - _service.Invoking(s => s.GetAvatar("")).Should().Throw() - .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); - } + await _service.Awaiting(s => s.GetAvatar(username)).Should().ThrowAsync(exceptionType); - [Fact] - public void GetAvatar_ShouldThrow_UserNotExistException() - { - const string username = "usernotexist"; - _service.Awaiting(s => s.GetAvatar(username)).Should().Throw() - .Where(e => e.Username == username); } [Fact] public async Task GetAvatar_ShouldReturn_Default() { + var mockAvatar = CreateMockAvatarInfo("aaa"); + _mockDefaultAvatarProvider.Setup(p => p.GetDefaultAvatar()).ReturnsAsync(mockAvatar); string username = MockUser.User.Username; - (await _service.GetAvatar(username)).Avatar.Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatar()).Avatar); + (await _service.GetAvatar(username)).Should().BeEquivalentTo(mockAvatar); } [Fact] public async Task GetAvatar_ShouldReturn_Data() { string username = MockUser.User.Username; - + var mockAvatarEntity = CreateMockAvatarEntity("aaa"); { - // create mock data var context = _database.DatabaseContext; var user = await context.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync(); - Set(user.Avatar, MockAvatarEntity1); + user.Avatar = mockAvatarEntity; await context.SaveChangesAsync(); } - (await _service.GetAvatar(username)).Should().BeEquivalentTo(ToAvatarInfo(MockAvatarEntity1)); + (await _service.GetAvatar(username)).Should().BeEquivalentTo(ToAvatarInfo(mockAvatarEntity)); } - [Fact] - public void SetAvatar_ShouldThrow_ArgumentException() + public static IEnumerable SetAvatar_ShouldThrow_Data() { - var avatar = ToAvatar(MockAvatarEntity1); - // no need to await because arguments are checked syncronizedly. - _service.Invoking(s => s.SetAvatar(null, avatar)).Should().Throw() - .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); - _service.Invoking(s => s.SetAvatar("", avatar)).Should().Throw() - .Where(e => e.ParamName == "username" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); - - _service.Invoking(s => s.SetAvatar("aaa", new Avatar { Type = null, Data = new[] { (byte)0x00 } })).Should().Throw() - .Where(e => e.ParamName == "avatar" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); - _service.Invoking(s => s.SetAvatar("aaa", new Avatar { Type = "", Data = new[] { (byte)0x00 } })).Should().Throw() - .Where(e => e.ParamName == "avatar" && e.Message.Contains("empty", StringComparison.OrdinalIgnoreCase)); - - _service.Invoking(s => s.SetAvatar("aaa", new Avatar { Type = "aaa", Data = null })).Should().Throw() - .Where(e => e.ParamName == "avatar" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase)); + yield return new object[] { null, null, typeof(ArgumentNullException) }; + yield return new object[] { "", null, typeof(UsernameBadFormatException) }; + yield return new object[] { "u!u", null, typeof(UsernameBadFormatException) }; + yield return new object[] { null, new Avatar { Type = null, Data = new[] { (byte)0x00 } }, typeof(ArgumentException) }; + yield return new object[] { null, new Avatar { Type = "", Data = new[] { (byte)0x00 } }, typeof(ArgumentException) }; + yield return new object[] { null, new Avatar { Type = "aaa", Data = null }, typeof(ArgumentException) }; + yield return new object[] { "usernotexist", null, typeof(UserNotExistException) }; } - [Fact] - public void SetAvatar_ShouldThrow_UserNotExistException() + [Theory] + [MemberData(nameof(SetAvatar_ShouldThrow_Data))] + public async Task SetAvatar_ShouldThrow(string username, Avatar avatar, Type exceptionType) { - const string username = "usernotexist"; - _service.Awaiting(s => s.SetAvatar(username, ToAvatar(MockAvatarEntity1))).Should().Throw() - .Where(e => e.Username == username); + await _service.Awaiting(s => s.SetAvatar(username, avatar)).Should().ThrowAsync(exceptionType); } [Fact] @@ -290,27 +250,43 @@ namespace Timeline.Tests var user = await _database.DatabaseContext.Users.Where(u => u.Name == username).Include(u => u.Avatar).SingleAsync(); + var avatar1 = CreateMockAvatar("aaa"); + var avatar2 = CreateMockAvatar("bbb"); + + string etag1 = "etagaaa"; + string etag2 = "etagbbb"; + + DateTime dateTime1 = DateTime.Now.AddSeconds(2); + DateTime dateTime2 = DateTime.Now.AddSeconds(10); + DateTime dateTime3 = DateTime.Now.AddSeconds(20); + // create - var avatar1 = ToAvatar(MockAvatarEntity1); + _mockETagGenerator.Setup(g => g.Generate(avatar1.Data)).ReturnsAsync(etag1); + _mockClock.Setup(c => c.GetCurrentTime()).Returns(dateTime1); await _service.SetAvatar(username, avatar1); user.Avatar.Should().NotBeNull(); user.Avatar.Type.Should().Be(avatar1.Type); user.Avatar.Data.Should().Equal(avatar1.Data); - user.Avatar.ETag.Should().NotBeNull(); + user.Avatar.ETag.Should().Be(etag1); + user.Avatar.LastModified.Should().Be(dateTime1); // modify - var avatar2 = ToAvatar(MockAvatarEntity2); + _mockETagGenerator.Setup(g => g.Generate(avatar2.Data)).ReturnsAsync(etag2); + _mockClock.Setup(c => c.GetCurrentTime()).Returns(dateTime2); await _service.SetAvatar(username, avatar2); user.Avatar.Should().NotBeNull(); - user.Avatar.Type.Should().Be(MockAvatarEntity2.Type); - user.Avatar.Data.Should().Equal(MockAvatarEntity2.Data); - user.Avatar.ETag.Should().NotBeNull(); + user.Avatar.Type.Should().Be(avatar2.Type); + user.Avatar.Data.Should().Equal(avatar2.Data); + user.Avatar.ETag.Should().Be(etag2); + user.Avatar.LastModified.Should().Be(dateTime2); // delete + _mockClock.Setup(c => c.GetCurrentTime()).Returns(dateTime3); await _service.SetAvatar(username, null); user.Avatar.Type.Should().BeNull(); user.Avatar.Data.Should().BeNull(); user.Avatar.ETag.Should().BeNull(); + user.Avatar.LastModified.Should().Be(dateTime3); } } } diff --git a/Timeline/Entities/UserAvatar.cs b/Timeline/Entities/UserAvatar.cs index 3b5388aa..a5b18b94 100644 --- a/Timeline/Entities/UserAvatar.cs +++ b/Timeline/Entities/UserAvatar.cs @@ -24,17 +24,5 @@ namespace Timeline.Entities public DateTime LastModified { get; set; } public long UserId { get; set; } - - public static UserAvatar Create(DateTime lastModified) - { - return new UserAvatar - { - Id = 0, - Data = null, - Type = null, - ETag = null, - LastModified = lastModified - }; - } } } diff --git a/Timeline/Resources/Services/UserAvatarService.Designer.cs b/Timeline/Resources/Services/UserAvatarService.Designer.cs index cabc9ede..6ee6fef9 100644 --- a/Timeline/Resources/Services/UserAvatarService.Designer.cs +++ b/Timeline/Resources/Services/UserAvatarService.Designer.cs @@ -70,11 +70,11 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to Type of avatar is null.. + /// Looks up a localized string similar to Type of avatar is null or empty.. /// - internal static string ArgumentAvatarTypeNull { + internal static string ArgumentAvatarTypeNullOrEmpty { get { - return ResourceManager.GetString("ArgumentAvatarTypeNull", resourceCulture); + return ResourceManager.GetString("ArgumentAvatarTypeNullOrEmpty", resourceCulture); } } diff --git a/Timeline/Resources/Services/UserAvatarService.resx b/Timeline/Resources/Services/UserAvatarService.resx index ab6389ff..3269bf13 100644 --- a/Timeline/Resources/Services/UserAvatarService.resx +++ b/Timeline/Resources/Services/UserAvatarService.resx @@ -120,8 +120,8 @@ Data of avatar is null. - - Type of avatar is null. + + Type of avatar is null or empty. Database corupted! One of type and data of a avatar is null but the other is not. diff --git a/Timeline/Services/ETagGenerator.cs b/Timeline/Services/ETagGenerator.cs index e518f01f..d328ea20 100644 --- a/Timeline/Services/ETagGenerator.cs +++ b/Timeline/Services/ETagGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; +using System.Threading.Tasks; namespace Timeline.Services { @@ -11,7 +12,7 @@ namespace Timeline.Services /// The source data. /// The generated etag. /// Thrown if is null. - string Generate(byte[] source); + Task Generate(byte[] source); } public sealed class ETagGenerator : IETagGenerator, IDisposable @@ -24,12 +25,12 @@ namespace Timeline.Services _sha1 = SHA1.Create(); } - public string Generate(byte[] source) + public Task Generate(byte[] source) { if (source == null) throw new ArgumentNullException(nameof(source)); - return Convert.ToBase64String(_sha1.ComputeHash(source)); + return Task.Run(() => Convert.ToBase64String(_sha1.ComputeHash(source))); } private bool _disposed = false; // To detect redundant calls diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index 4c65a0fa..ff80003c 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -118,7 +118,7 @@ namespace Timeline.Services { _cacheData = await File.ReadAllBytesAsync(path); _cacheLastModified = File.GetLastWriteTime(path); - _cacheETag = _eTagGenerator.Generate(_cacheData); + _cacheETag = await _eTagGenerator.Generate(_cacheData); } } @@ -179,12 +179,15 @@ namespace Timeline.Services private readonly UsernameValidator _usernameValidator; + private readonly IClock _clock; + public UserAvatarService( ILogger logger, DatabaseContext database, IDefaultUserAvatarProvider defaultUserAvatarProvider, IUserAvatarValidator avatarValidator, - IETagGenerator eTagGenerator) + IETagGenerator eTagGenerator, + IClock clock) { _logger = logger; _database = database; @@ -192,6 +195,7 @@ namespace Timeline.Services _avatarValidator = avatarValidator; _eTagGenerator = eTagGenerator; _usernameValidator = new UsernameValidator(); + _clock = clock; } public async Task GetAvatarETag(string username) @@ -245,8 +249,8 @@ namespace Timeline.Services { if (avatar.Data == null) throw new ArgumentException(Resources.Services.UserAvatarService.ArgumentAvatarDataNull, nameof(avatar)); - if (avatar.Type == null) - throw new ArgumentException(Resources.Services.UserAvatarService.ArgumentAvatarTypeNull, nameof(avatar)); + if (string.IsNullOrEmpty(avatar.Type)) + throw new ArgumentException(Resources.Services.UserAvatarService.ArgumentAvatarTypeNullOrEmpty, nameof(avatar)); } var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username); @@ -263,7 +267,7 @@ namespace Timeline.Services avatarEntity.Data = null; avatarEntity.Type = null; avatarEntity.ETag = null; - avatarEntity.LastModified = DateTime.Now; + avatarEntity.LastModified = _clock.GetCurrentTime(); await _database.SaveChangesAsync(); _logger.LogInformation(Resources.Services.UserAvatarService.LogUpdateEntity); } @@ -278,8 +282,9 @@ namespace Timeline.Services } avatarEntity!.Type = avatar.Type; avatarEntity.Data = avatar.Data; - avatarEntity.ETag = _eTagGenerator.Generate(avatar.Data); - avatarEntity.LastModified = DateTime.Now; + avatarEntity.ETag = await _eTagGenerator.Generate(avatar.Data); + avatarEntity.LastModified = _clock.GetCurrentTime(); + avatarEntity.UserId = userId; if (create) { _database.UserAvatars.Add(avatarEntity); -- cgit v1.2.3