aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-10-24 19:57:59 +0800
committer杨宇千 <crupest@outlook.com>2019-10-24 19:57:59 +0800
commit912455ac19161533205c2fe56b91ff4595ea4fdb (patch)
treeb8ddc07077f9ac767a27f811db4f20ba88cc5087
parent89c106169bd2a16310fdaa6e0c48a3402d97de3a (diff)
downloadtimeline-912455ac19161533205c2fe56b91ff4595ea4fdb.tar.gz
timeline-912455ac19161533205c2fe56b91ff4595ea4fdb.tar.bz2
timeline-912455ac19161533205c2fe56b91ff4595ea4fdb.zip
...
-rw-r--r--Timeline.Tests/DatabaseTest.cs14
-rw-r--r--Timeline.Tests/GlobalSuppressions.cs2
-rw-r--r--Timeline.Tests/Helpers/AsyncFunctionAssertionsExtensions.cs16
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTest.cs20
-rw-r--r--Timeline.Tests/Mock/Data/TestUsers.cs2
-rw-r--r--Timeline.Tests/UserAvatarServiceTest.cs206
-rw-r--r--Timeline/Entities/UserAvatar.cs12
-rw-r--r--Timeline/Resources/Services/UserAvatarService.Designer.cs6
-rw-r--r--Timeline/Resources/Services/UserAvatarService.resx4
-rw-r--r--Timeline/Services/ETagGenerator.cs7
-rw-r--r--Timeline/Services/UserAvatarService.cs19
11 files changed, 162 insertions, 146 deletions
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<AndConstraint<ObjectAssertions>> ThrowAsync(this AsyncFunctionAssertions assertions, Type exceptionType, string because = "", params object[] becauseArgs)
+ {
+ return (await assertions.ThrowAsync<Exception>(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<string> GetDefaultAvatarETag()
- {
- return Task.FromResult(ETag);
- }
-
- public Task<AvatarInfo> GetDefaultAvatar()
- {
- return Task.FromResult(AvatarInfo);
- }
- }
-
- public class MockUserAvatarValidator : IUserAvatarValidator
- {
- public Task Validate(Avatar avatar)
- {
- return Task.CompletedTask;
- }
- }
-
public class UserAvatarValidatorTest : IClassFixture<UserAvatarValidator>
{
private readonly UserAvatarValidator _validator;
@@ -106,22 +80,30 @@ namespace Timeline.Tests
}
}
- public class UserAvatarServiceTest : IDisposable, IClassFixture<MockDefaultUserAvatarProvider>, IClassFixture<MockUserAvatarValidator>
+ 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<IDefaultUserAvatarProvider> _mockDefaultAvatarProvider;
+ private readonly Mock<IUserAvatarValidator> _mockValidator;
+ private readonly Mock<IETagGenerator> _mockETagGenerator;
+ private readonly Mock<IClock> _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<IDefaultUserAvatarProvider>();
+ _mockValidator = new Mock<IUserAvatarValidator>();
+ _mockETagGenerator = new Mock<IETagGenerator>();
+ _mockClock = new Mock<IClock>();
_database = new TestDatabase();
- _eTagGenerator = new ETagGenerator();
-
- _service = new UserAvatarService(NullLogger<UserAvatarService>.Instance, _database.DatabaseContext, _mockDefaultUserAvatarProvider, mockUserAvatarValidator, _eTagGenerator);
+ _service = new UserAvatarService(NullLogger<UserAvatarService>.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<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.GetAvatarETag("")).Should().Throw<ArgumentException>()
- .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<UserNotExistException>()
- .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<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.GetAvatar("")).Should().Throw<ArgumentException>()
- .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<UserNotExistException>()
- .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<object[]> 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<ArgumentException>()
- .Where(e => e.ParamName == "username" && e.Message.Contains("null", StringComparison.OrdinalIgnoreCase));
- _service.Invoking(s => s.SetAvatar("", avatar)).Should().Throw<ArgumentException>()
- .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<ArgumentException>()
- .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<ArgumentException>()
- .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<ArgumentException>()
- .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<UserNotExistException>()
- .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 {
}
/// <summary>
- /// 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..
/// </summary>
- 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 name="ArgumentAvatarDataNull" xml:space="preserve">
<value>Data of avatar is null.</value>
</data>
- <data name="ArgumentAvatarTypeNull" xml:space="preserve">
- <value>Type of avatar is null.</value>
+ <data name="ArgumentAvatarTypeNullOrEmpty" xml:space="preserve">
+ <value>Type of avatar is null or empty.</value>
</data>
<data name="DatabaseCorruptedDataAndTypeNotSame" xml:space="preserve">
<value>Database corupted! One of type and data of a avatar is null but the other is not.</value>
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
/// <param name="source">The source data.</param>
/// <returns>The generated etag.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
- string Generate(byte[] source);
+ Task<string> 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<string> 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<UserAvatarService> 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<string> 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);