Skip to content

Commit 1a66b82

Browse files
committed
Allow time zone to be omitted when creating users via API
Since we now have a default user time zone configuration option, it no longer makes sense to require that the time zone be specified when creating a new user via the API or indeed on any call to `IUserManager.CreateUser`.
1 parent 6bcfb84 commit 1a66b82

File tree

4 files changed

+51
-5
lines changed

4 files changed

+51
-5
lines changed

src/Buttercup.Application.Tests/UserManagerTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Buttercup.TestUtils;
33
using Microsoft.AspNetCore.Identity;
44
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.Extensions.Options;
56
using Microsoft.Extensions.Time.Testing;
67
using Moq;
78
using Xunit;
@@ -13,6 +14,7 @@ public sealed class UserManagerTests : DatabaseTests<DatabaseCollection>
1314
{
1415
private readonly ModelFactory modelFactory = new();
1516

17+
private readonly GlobalizationOptions globalizationOptions;
1618
private readonly Mock<IPasswordHasher<User>> passwordHasherMock = new();
1719
private readonly Mock<IRandomTokenGenerator> randomTokenGeneratorMock = new();
1820
private readonly FakeTimeProvider timeProvider;
@@ -21,9 +23,15 @@ public sealed class UserManagerTests : DatabaseTests<DatabaseCollection>
2123
public UserManagerTests(DatabaseFixture<DatabaseCollection> databaseFixture)
2224
: base(databaseFixture)
2325
{
26+
this.globalizationOptions = new GlobalizationOptions
27+
{
28+
DefaultUserTimeZone = this.modelFactory.NextString("default-time-zone"),
29+
};
2430
this.timeProvider = new(this.modelFactory.NextDateTime());
31+
2532
this.userManager = new(
2633
this.DatabaseFixture,
34+
Options.Create(this.globalizationOptions),
2735
this.passwordHasherMock.Object,
2836
this.randomTokenGeneratorMock.Object,
2937
this.timeProvider);
@@ -87,6 +95,31 @@ public async Task CreateUser_Success(bool isAdmin)
8795
Assert.Equal(expectedAuditEntry, actualAuditEntry);
8896
}
8997

98+
[Fact]
99+
public async Task CreateUser_NoTimeZoneSpecified()
100+
{
101+
var currentUser = this.modelFactory.BuildUser();
102+
await this.DatabaseFixture.InsertEntities(currentUser);
103+
104+
this.randomTokenGeneratorMock.Setup(x => x.Generate(2)).Returns(
105+
this.modelFactory.NextToken(8));
106+
107+
var id = await this.userManager.CreateUser(
108+
new()
109+
{
110+
Name = this.modelFactory.NextString("name"),
111+
Email = this.modelFactory.NextEmail(),
112+
TimeZone = null,
113+
},
114+
currentUser.Id,
115+
null);
116+
117+
using var dbContext = this.DatabaseFixture.CreateDbContext();
118+
119+
var user = await dbContext.Users.GetAsync(id);
120+
Assert.Equal(this.globalizationOptions.DefaultUserTimeZone, user.TimeZone);
121+
}
122+
90123
[Fact]
91124
public async Task CreateUser_EmailNotUnique()
92125
{

src/Buttercup.Application/NewUserAttributes.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ public sealed record NewUserAttributes
3232
/// <summary>
3333
/// Gets or sets the user's time zone.
3434
/// </summary>
35+
/// <remarks>
36+
/// If this property is left unspecified, the new user's time zone will be set to <see
37+
/// cref="GlobalizationOptions.DefaultUserTimeZone"/>.
38+
/// </remarks>
3539
/// <value>
3640
/// The user's time zone as a TZID (e.g. 'Europe/London').
3741
/// </value>
38-
[Required(ErrorMessage = "Error_RequiredField")]
3942
[StringLength(50, ErrorMessage = "Error_TooManyCharacters")]
4043
[TimeZone(ErrorMessage = "Error_InvalidTimeZone")]
41-
public string TimeZone { get; init; } = string.Empty;
44+
public string? TimeZone { get; init; }
4245

4346
/// <summary>
4447
/// Gets or sets a value indicating whether the user is an administrator.

src/Buttercup.Application/UserManager.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
using Microsoft.AspNetCore.Identity;
55
using Microsoft.EntityFrameworkCore;
66
using Microsoft.EntityFrameworkCore.Query;
7+
using Microsoft.Extensions.Options;
78

89
namespace Buttercup.Application;
910

1011
internal sealed class UserManager(
1112
IDbContextFactory<AppDbContext> dbContextFactory,
13+
IOptions<GlobalizationOptions> globalizationOptions,
1214
IPasswordHasher<User> passwordHasher,
1315
IRandomTokenGenerator randomTokenGenerator,
1416
TimeProvider timeProvider)
1517
: IUserManager
1618
{
1719
private readonly IDbContextFactory<AppDbContext> dbContextFactory = dbContextFactory;
20+
private readonly GlobalizationOptions globalizationOptions = globalizationOptions.Value;
1821
private readonly IPasswordHasher<User> passwordHasher = passwordHasher;
1922
private readonly IRandomTokenGenerator randomTokenGenerator = randomTokenGenerator;
2023
private readonly TimeProvider timeProvider = timeProvider;
@@ -28,7 +31,7 @@ public async Task<long> CreateUser(
2831
Name = attributes.Name,
2932
Email = attributes.Email,
3033
SecurityStamp = this.GenerateSecurityStamp(),
31-
TimeZone = attributes.TimeZone,
34+
TimeZone = attributes.TimeZone ?? this.globalizationOptions.DefaultUserTimeZone,
3235
IsAdmin = attributes.IsAdmin,
3336
Created = timestamp,
3437
Modified = timestamp,

src/Buttercup.Web.Tests/Api/CreateUserTests.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Buttercup.Application;
22
using Buttercup.Web.TestUtils;
33
using HotChocolate;
4+
using Microsoft.Extensions.Options;
45
using Xunit;
56

67
namespace Buttercup.Web.Api;
@@ -43,6 +44,9 @@ public async Task CreatingUser(bool isAdmin)
4344
[Fact]
4445
public async Task CreatingUserWithDefaultValues()
4546
{
47+
var globalizationOptions =
48+
this.AppFactory.Services.GetRequiredService<IOptions<GlobalizationOptions>>().Value;
49+
4650
var currentUser = this.ModelFactory.BuildUser() with { IsAdmin = true };
4751
await this.DatabaseFixture.InsertEntities(currentUser);
4852

@@ -52,14 +56,17 @@ public async Task CreatingUserWithDefaultValues()
5256
{
5357
Name = this.ModelFactory.NextString("name"),
5458
Email = this.ModelFactory.NextEmail(),
55-
TimeZone = "America/New_York",
5659
};
5760

5861
using var response = await PostCreateUserMutation(client, attributes);
5962
using var document = await response.Content.ReadAsJsonDocument();
6063

6164
var createUserElement = ApiAssert.SuccessResponse(document).GetProperty("createUser");
62-
Assert.False(createUserElement.GetProperty("user").GetProperty("isAdmin").GetBoolean());
65+
var userElement = createUserElement.GetProperty("user");
66+
Assert.Equal(
67+
globalizationOptions.DefaultUserTimeZone,
68+
userElement.GetProperty("timeZone").GetString());
69+
Assert.False(userElement.GetProperty("isAdmin").GetBoolean());
6370
JsonAssert.ValueIsNull(createUserElement.GetProperty("errors"));
6471
}
6572

0 commit comments

Comments
 (0)