Migrated some of the UserControllerTests

Signed-off-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Bjarke Berg
2020-06-22 14:11:01 +02:00
parent 44497ffdd4
commit 0714fc4734
11 changed files with 947 additions and 586 deletions

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.Membership;
using Umbraco.Tests.Common.Builders.Interfaces;
@@ -41,6 +43,7 @@ namespace Umbraco.Tests.Common.Builders
private int? _sessionTimeout;
private int[] _startContentIds;
private int[] _startMediaIds;
private readonly List<UserGroupBuilder<UserBuilder<TParent>>> _userGroupBuilders = new List<UserGroupBuilder<UserBuilder<TParent>>>();
public UserBuilder(TParent parentBuilder) : base(parentBuilder)
{
@@ -82,6 +85,8 @@ namespace Umbraco.Tests.Common.Builders
return this;
}
public UserBuilder<TParent> WithSessionTimeout(int sessionTimeout)
{
_sessionTimeout = sessionTimeout;
@@ -113,7 +118,7 @@ namespace Umbraco.Tests.Common.Builders
public override User Build()
{
var id = _id ?? 1;
var id = _id ?? 0;
var defaultLang = _defaultLang ?? "en";
var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(defaultLang).Build();
var key = _key ?? Guid.NewGuid();
@@ -134,8 +139,9 @@ namespace Umbraco.Tests.Common.Builders
var sessionTimeout = _sessionTimeout ?? 0;
var startContentIds = _startContentIds ?? new int[0];
var startMediaIds = _startMediaIds ?? new int[0];
var groups = _userGroupBuilders.Select(x => x.Build());
return new User(
var result = new User(
globalSettings,
name,
email,
@@ -158,8 +164,25 @@ namespace Umbraco.Tests.Common.Builders
StartContentIds = startContentIds,
StartMediaIds = startMediaIds,
};
foreach (var readOnlyUserGroup in groups)
{
result.AddGroup(readOnlyUserGroup.ToReadOnlyGroup());
}
return result;
}
public UserGroupBuilder<UserBuilder<TParent>> AddUserGroup()
{
var builder = new UserGroupBuilder<UserBuilder<TParent>>(this);
_userGroupBuilders.Add(builder);
return builder;
}
int? IWithIdBuilder.Id
{
get => _id;

View File

@@ -92,7 +92,7 @@ namespace Umbraco.Tests.Common.Builders
public override IUserGroup Build()
{
var id = _id ?? 1;
var id = _id ?? 0;
var name = _name ?? ("TestUserGroup" + _suffix);
var alias = _alias ?? ("testUserGroup" + _suffix);
var userCount = _userCount ?? 0;

View File

@@ -0,0 +1,321 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.Common.Builders.Extensions;
using Umbraco.Tests.Testing;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Common.Formatters;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Tests.Integration.TestServerTest.Controllers
{
// [Explicit("We need to fix the tests on buildserver and when running multiple tests in one run")]
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class UsersControllerTests : UmbracoTestServerTestBase
{
[Test]
public async Task Save_User()
{
var url = PrepareUrl<UsersController>(x => x.PostSaveUser(null));
var userService = GetRequiredService<IUserService>();
var user = new UserBuilder()
.AddUserGroup()
.WithAlias("writer") // Needs to be an existing alias
.Done()
.Build();
userService.Save(user);
var userSave = new UserSave
{
Id = user.Id,
Email = user.Email,
Username = user.Username,
Culture = "en",
Name = user.Name,
UserGroups = user.Groups.Select(x => x.Alias).ToArray()
};
// Act
var response = await Client.PostAsync(url,
new StringContent(JsonConvert.SerializeObject(userSave), Encoding.UTF8,
MediaTypeNames.Application.Json));
// Assert
Assert.Multiple(() =>
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix);
var actual = JsonConvert.DeserializeObject<UserDisplay>(body, new JsonSerializerSettings
{
ContractResolver = new IgnoreRequiredAttributsResolver()
});
Assert.AreEqual(userSave.Name, actual.Name);
Assert.AreEqual(userSave.Id, actual.Id);
Assert.AreEqual(userSave.Email, actual.Email);
var userGroupAliases = actual.UserGroups.Select(x => x.Alias).ToArray();
CollectionAssert.AreEquivalent(userSave.UserGroups, userGroupAliases);
});
}
[Test]
public async Task GetPagedUsers_Empty()
{
//We get page 2 to force an empty response because there always in the useradmin user
var url = PrepareUrl<UsersController>(x => x.GetPagedUsers(2, 10, "username", Direction.Ascending, null, null, string.Empty));
// Act
var response = await Client.GetAsync(url);
var body = await response.Content.ReadAsStringAsync();
body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var actual = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(body, new JsonSerializerSettings
{
ContractResolver = new IgnoreRequiredAttributsResolver()
});
Assert.Multiple(() =>
{
Assert.IsNotNull(actual);
Assert.AreEqual(1, actual.TotalItems);
CollectionAssert.IsEmpty(actual.Items);
});
}
[Test]
public async Task GetPagedUsers_11()
{
var totalNumberOfUsers = 11;
var pageSize = totalNumberOfUsers - 1;
var url = PrepareUrl<UsersController>(x => x.GetPagedUsers(1, pageSize, "username", Direction.Ascending, null, null, string.Empty));
var userService = GetRequiredService<IUserService>();
for (int i = 1; i < totalNumberOfUsers; i++) // We already has admin user = -1, so we start from 1
{
var user = new UserBuilder()
.WithName($"Test user {i}")
.AddUserGroup()
.WithAlias("writer") // Needs to be an existing alias
.Done()
.Build();
userService.Save(user);
}
// Act
var response = await Client.GetAsync(url);
var body = await response.Content.ReadAsStringAsync();
body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var actual = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(body, new JsonSerializerSettings
{
ContractResolver = new IgnoreRequiredAttributsResolver()
});
Assert.Multiple(() =>
{
Assert.IsNotNull(actual);
Assert.AreEqual(totalNumberOfUsers, actual.TotalItems);
Assert.AreEqual(pageSize, actual.Items.Count());
});
}
[Test]
public async Task PostUnlockUsers_When_UserIds_Not_Supplied_Expect_Ok_Response()
{
var url = PrepareUrl<UsersController>(x => x.PostUnlockUsers(Array.Empty<int>()));
// Act
var response = await Client.PostAsync(url, new StringContent(string.Empty));
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
[Test]
public async Task PostUnlockUsers_When_User_Does_Not_Exist_Expect_InvalidOperationException()
{
var userId = 42; // Must not exist
var url = PrepareUrl<UsersController>(x => x.PostUnlockUsers(new []{userId}));
// Act
var response = await Client.PostAsync(url, new StringContent(string.Empty));
var body = await response.Content.ReadAsStringAsync();
body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix);
Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);
var actual = JsonConvert.DeserializeObject<ExceptionViewModel>(body, new JsonSerializerSettings
{
ContractResolver = new IgnoreRequiredAttributsResolver()
});
Assert.Multiple(() =>
{
var expected = new InvalidOperationException();
Assert.IsNotNull(actual);
Assert.AreEqual(expected.GetType(), actual.ExceptionType);
Assert.AreEqual(expected.Message, actual.ExceptionMessage);
});
}
// [Test]
// public async Task PostUnlockUsers_When_User_Lockout_Update_Fails_Expect_Failure_Response()
// {
// var mockUserManager = CreateMockUserManager();
// var usersController = CreateSut(mockUserManager);
//
// const string expectedMessage = "identity error!";
// var user = new BackOfficeIdentityUser(
// new Mock<IGlobalSettings>().Object,
// 1,
// new List<IReadOnlyUserGroup>())
// {
// Name = "bob"
// };
//
// mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
// .ReturnsAsync(user);
// mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user, It.IsAny<DateTimeOffset?>()))
// .ReturnsAsync(IdentityResult.Failed(new IdentityError {Description = expectedMessage}));
//
// var response = await usersController.PostUnlockUsers(new[] { 1 });
//
// Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
// Assert.True(response.Headers.TryGetValues("X-Status-Reason", out var values));
// Assert.True(values.Contains("Validation failed"));
//
// var responseContent = response.Content as ObjectContent<HttpError>;
// var responseValue = responseContent?.Value as HttpError;
// Assert.NotNull(responseValue);
// Assert.True(responseValue.Message.Contains(expectedMessage));
// Assert.True(responseValue.Message.Contains(user.Id.ToString()));
// }
//
// [Test]
// public async Task PostUnlockUsers_When_One_UserId_Supplied_Expect_User_Locked_Out_With_Correct_Response_Message()
// {
// var mockUserManager = CreateMockUserManager();
// var usersController = CreateSut(mockUserManager);
//
// var user = new BackOfficeIdentityUser(
// new Mock<IGlobalSettings>().Object,
// 1,
// new List<IReadOnlyUserGroup>())
// {
// Name = "bob"
// };
//
// mockUserManager.Setup(x => x.FindByIdAsync(user.Id.ToString()))
// .ReturnsAsync(user);
// mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user, It.IsAny<DateTimeOffset>()))
// .ReturnsAsync(IdentityResult.Success)
// .Verifiable();
//
// var response = await usersController.PostUnlockUsers(new[] { user.Id });
//
// Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
//
// var responseContent = response.Content as ObjectContent<SimpleNotificationModel>;
// var notifications = responseContent?.Value as SimpleNotificationModel;
// Assert.NotNull(notifications);
// Assert.AreEqual(user.Name, notifications.Message);
// mockUserManager.Verify();
// }
//
// [Test]
// public async Task PostUnlockUsers_When_Multiple_UserIds_Supplied_Expect_User_Locked_Out_With_Correct_Response_Message()
// {
// var mockUserManager = CreateMockUserManager();
// var usersController = CreateSut(mockUserManager);
//
// var user1 = new BackOfficeIdentityUser(
// new Mock<IGlobalSettings>().Object,
// 1,
// new List<IReadOnlyUserGroup>())
// {
// Name = "bob"
// };
// var user2 = new BackOfficeIdentityUser(
// new Mock<IGlobalSettings>().Object,
// 2,
// new List<IReadOnlyUserGroup>())
// {
// Name = "alice"
// };
// var userIdsToLock = new[] {user1.Id, user2.Id};
//
// mockUserManager.Setup(x => x.FindByIdAsync(user1.Id.ToString()))
// .ReturnsAsync(user1);
// mockUserManager.Setup(x => x.FindByIdAsync(user2.Id.ToString()))
// .ReturnsAsync(user2);
// mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user1, It.IsAny<DateTimeOffset>()))
// .ReturnsAsync(IdentityResult.Success)
// .Verifiable();
// mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user2, It.IsAny<DateTimeOffset>()))
// .ReturnsAsync(IdentityResult.Success)
// .Verifiable();
//
// var response = await usersController.PostUnlockUsers(userIdsToLock);
//
// Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
//
// var responseContent = response.Content as ObjectContent<SimpleNotificationModel>;
// var notifications = responseContent?.Value as SimpleNotificationModel;
// Assert.NotNull(notifications);
// Assert.AreEqual(userIdsToLock.Length.ToString(), notifications.Message);
// mockUserManager.Verify();
// }
//
// [Test]
// public async Task GetPagedUsers_Fips()
// {
// await RunFipsTest("GetPagedUsers", mock =>
// {
// var users = MockedUser.CreateMulipleUsers(10);
// long outVal = 10;
// mock.Setup(service => service.GetAll(
// It.IsAny<long>(), It.IsAny<int>(), out outVal, It.IsAny<string>(), It.IsAny<Direction>(),
// It.IsAny<UserState[]>(), It.IsAny<string[]>(), It.IsAny<string[]>(), It.IsAny<IQuery<IUser>>()))
// .Returns(() => users);
// }, response =>
// {
// var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
// Assert.AreEqual(10, obj.TotalItems);
// Assert.AreEqual(10, obj.Items.Count());
// });
// }
//
// [Test]
// public async Task GetById_Fips()
// {
// const int mockUserId = 1234;
// var user = MockedUser.CreateUser();
//
// await RunFipsTest("GetById", mock =>
// {
// mock.Setup(service => service.GetUserById(1234))
// .Returns((int i) => i == mockUserId ? user : null);
// }, response =>
// {
// var obj = JsonConvert.DeserializeObject<UserDisplay>(response.Item2);
// Assert.AreEqual(user.Username, obj.Username);
// Assert.AreEqual(user.Email, obj.Email);
// }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}");
// }
}
}

View File

@@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="LightInject.Microsoft.DependencyInjection" Version="3.3.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.13.1" />

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Umbraco.Core;
using Microsoft.AspNetCore.Routing;
@@ -56,10 +58,17 @@ namespace Umbraco.Extensions
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, object id = null)
where T : UmbracoApiControllerBase
{
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), id);
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), new Dictionary<string, object>()
{
["id"] = id
});
}
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, IDictionary<string, object> values)
where T : UmbracoApiControllerBase
{
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), values);
}
public static string GetUmbracoApiServiceBaseUrl<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
where T : UmbracoApiControllerBase
@@ -81,35 +90,30 @@ namespace Umbraco.Extensions
/// <param name="area"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, string controllerName, string area, object id = null)
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, string controllerName, string area, IDictionary<string,object> dict = null)
{
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
if (area.IsNullOrWhiteSpace())
if (dict is null)
{
if (id == null)
{
return linkGenerator.GetPathByAction(actionName, controllerName);
}
else
{
return linkGenerator.GetPathByAction(actionName, controllerName, new { id = id });
}
dict = new Dictionary<string, object>();
}
else
if (!area.IsNullOrWhiteSpace())
{
if (id == null)
{
return linkGenerator.GetPathByAction(actionName, controllerName, new { area = area });
}
else
{
return linkGenerator.GetPathByAction(actionName, controllerName, new { area = area, id = id });
}
dict["area"] = area;
}
var values = dict.Aggregate(new ExpandoObject() as IDictionary<string, object>,
(a, p) => { a.Add(p.Key, p.Value); return a; });
return linkGenerator.GetPathByAction(actionName, controllerName, values);
}
/// <summary>
@@ -120,7 +124,7 @@ namespace Umbraco.Extensions
/// <param name="apiControllerType"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, Type apiControllerType, object id = null)
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, Type apiControllerType, IDictionary<string,object> values = null)
{
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
@@ -137,7 +141,7 @@ namespace Umbraco.Extensions
//set the area to the plugin area
area = metaData.AreaName;
}
return linkGenerator.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, id);
return linkGenerator.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, values);
}
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
@@ -155,7 +159,7 @@ namespace Umbraco.Extensions
{
return linkGenerator.GetUmbracoApiService<T>(method.Name);
}
return linkGenerator.GetUmbracoApiService<T>(method.Name, methodParams.Values.First());
return linkGenerator.GetUmbracoApiService<T>(method.Name, methodParams);
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Umbraco.Web.Common.Filters
{
public class ExceptionViewModel
{
public string ExceptionMessage { get; set; }
public Type ExceptionType { get; set; }
public string StackTrace { get; set; }
}
}

View File

@@ -45,23 +45,15 @@ namespace Umbraco.Web.Common.Filters
private object GetModel(Exception ex)
{
object error;
var error = new ExceptionViewModel
{
ExceptionMessage = ex.Message
};
if (_hostingEnvironment.IsDebugMode)
{
error = new
{
ExceptionMessage = ex.Message,
ExceptionType = ex.GetType(),
StackTrace = ex.StackTrace
};
}
else
{
error = new
{
ExceptionMessage = ex.Message
};
error.ExceptionType = ex.GetType();
error.StackTrace = ex.StackTrace;
}
return error;

View File

@@ -0,0 +1,19 @@
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Umbraco.Web.Common.Formatters
{
public class IgnoreRequiredAttributsResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.Required = Required.Default;
return property;
}
}
}

View File

@@ -9,6 +9,7 @@ using System.Buffers;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Umbraco.Web.Common.Formatters;
namespace Umbraco.Web.Common.ModelBinding
{
@@ -44,18 +45,7 @@ namespace Umbraco.Web.Common.ModelBinding
jsonOptions)
};
}
private class IgnoreRequiredAttributsResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.Required = Required.Default;
return property;
}
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Web.Security
public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker";
public BackOfficeOwinUserManager(
IPasswordConfiguration passwordConfiguration,
IUserPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver,
IUserStore<BackOfficeIdentityUser> store,
IOptions<BackOfficeIdentityOptions> optionsAccessor,
@@ -30,14 +30,14 @@ namespace Umbraco.Web.Security
BackOfficeIdentityErrorDescriber errors,
IDataProtectionProvider dataProtectionProvider,
ILogger<UserManager<BackOfficeIdentityUser>> logger)
: base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger)
: base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger, passwordConfiguration)
{
PasswordConfiguration = passwordConfiguration;
InitUserManager(this, dataProtectionProvider);
}
#region Static Create methods
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
/// </summary>
@@ -54,7 +54,7 @@ namespace Umbraco.Web.Security
ILogger<UserManager<BackOfficeIdentityUser>> logger)
{
var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper);
return Create(
passwordConfiguration,
ipResolver,
@@ -68,7 +68,7 @@ namespace Umbraco.Web.Security
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
/// </summary>
public static BackOfficeOwinUserManager Create(
IPasswordConfiguration passwordConfiguration,
IUserPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver,
IUserStore<BackOfficeIdentityUser> customUserStore,
BackOfficeIdentityErrorDescriber errors,
@@ -88,7 +88,7 @@ namespace Umbraco.Web.Security
options.Password.RequireDigit = passwordConfiguration.RequireDigit;
options.Password.RequireLowercase = passwordConfiguration.RequireLowercase;
options.Password.RequireUppercase = passwordConfiguration.RequireUppercase;
// Ensure Umbraco security stamp claim type is used
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
@@ -109,7 +109,7 @@ namespace Umbraco.Web.Security
new OptionsWrapper<BackOfficeIdentityOptions>(options),
userValidators,
passwordValidators,
new BackOfficeLookupNormalizer(),
new BackOfficeLookupNormalizer(),
errors,
dataProtectionProvider,
logger);