diff options
Diffstat (limited to 'Timeline.Tests/Helpers')
-rw-r--r-- | Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs | 75 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/MockUser.cs | 24 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/PrincipalHelper.cs (renamed from Timeline.Tests/Helpers/Authentication/PrincipalHelper.cs) | 46 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/TestApplication.cs | 2 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/TestClock.cs | 15 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/TestDatabase.cs | 89 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/UseCultureAttribute.cs | 143 |
7 files changed, 224 insertions, 170 deletions
diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs deleted file mode 100644 index 4048bb73..00000000 --- a/Timeline.Tests/Helpers/Authentication/AuthenticationExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Testing;
-using System;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Timeline.Models.Http;
-using Timeline.Tests.Mock.Data;
-
-namespace Timeline.Tests.Helpers.Authentication
-{
- public enum AuthType
- {
- None,
- User,
- Admin
- }
-
- 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 });
- return response.Should().HaveStatusCode(200)
- .And.HaveJsonBody<CreateTokenResponse>().Which;
- }
-
- 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> CreateClientAs<T>(this WebApplicationFactory<T> factory, MockUser user) where T : class
- {
- return CreateClientWithCredential(factory, user.Username, user.Password);
- }
-
- public static Task<HttpClient> CreateClientAsUser<T>(this WebApplicationFactory<T> factory) where T : class
- {
- return factory.CreateClientAs(MockUser.User);
- }
-
- public static Task<HttpClient> CreateClientAsAdmin<T>(this WebApplicationFactory<T> factory) where T : class
- {
- return factory.CreateClientAs(MockUser.Admin);
- }
-
- public static Task<HttpClient> CreateClientAs<T>(this WebApplicationFactory<T> factory, AuthType authType) where T : class
- {
- return authType switch
- {
- AuthType.None => Task.FromResult(factory.CreateDefaultClient()),
- AuthType.User => factory.CreateClientAsUser(),
- AuthType.Admin => factory.CreateClientAsAdmin(),
- _ => throw new InvalidOperationException("Unknown auth type.")
- };
- }
-
- 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;
- }
-}
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/Authentication/PrincipalHelper.cs b/Timeline.Tests/Helpers/PrincipalHelper.cs index 214472a2..89f3f7b1 100644 --- a/Timeline.Tests/Helpers/Authentication/PrincipalHelper.cs +++ b/Timeline.Tests/Helpers/PrincipalHelper.cs @@ -1,23 +1,23 @@ -using System.Linq;
-using System.Security.Claims;
-using Timeline.Models;
-
-namespace Timeline.Tests.Helpers.Authentication
-{
- 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;
- }
- }
-}
+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/TestApplication.cs b/Timeline.Tests/Helpers/TestApplication.cs index 5862f452..a624da6b 100644 --- a/Timeline.Tests/Helpers/TestApplication.cs +++ b/Timeline.Tests/Helpers/TestApplication.cs @@ -1,10 +1,8 @@ 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
{
diff --git a/Timeline.Tests/Helpers/TestClock.cs b/Timeline.Tests/Helpers/TestClock.cs new file mode 100644 index 00000000..12b320d3 --- /dev/null +++ b/Timeline.Tests/Helpers/TestClock.cs @@ -0,0 +1,15 @@ +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(); + } } } |