refactor(domain): introduce UserFactory and WorkplaceFactory for reconstitution

Consistent with ReservationFactory — moves Reconstitute out of the entity
into a dedicated factory class, and makes the reconstitution constructor
internal to restrict direct instantiation to within the domain assembly.
Adds unit tests for all three factories.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Robert van Diest
2026-03-27 17:13:55 +01:00
parent da698224a7
commit 4468e7b891
9 changed files with 182 additions and 10 deletions

View File

@@ -10,10 +10,7 @@ public class User : Entity
public bool IsApproved { get; private set; } public bool IsApproved { get; private set; }
public bool IsAdmin { get; private set; } public bool IsAdmin { get; private set; }
public static User Reconstitute(Guid id, string email, string name, string passwordHash, bool isApproved, bool isAdmin) => internal User(Guid id, string email, string name, string passwordHash, bool isApproved, bool isAdmin) : base(id)
new(id, email, name, passwordHash, isApproved, isAdmin);
private User(Guid id, string email, string name, string passwordHash, bool isApproved, bool isAdmin) : base(id)
{ {
Email = email; Email = email;
Name = name; Name = name;

View File

@@ -0,0 +1,7 @@
namespace Randall.Domain.Users;
public static class UserFactory
{
public static User Reconstitute(Guid id, string email, string name, string passwordHash, bool isApproved, bool isAdmin) =>
new(id, email, name, passwordHash, isApproved, isAdmin);
}

View File

@@ -8,10 +8,7 @@ public class Workplace : Entity
public string Location { get; private set; } public string Location { get; private set; }
public bool IsActive { get; private set; } public bool IsActive { get; private set; }
public static Workplace Reconstitute(Guid id, string name, string location, bool isActive) => internal Workplace(Guid id, string name, string location, bool isActive) : base(id)
new(id, name, location, isActive);
private Workplace(Guid id, string name, string location, bool isActive) : base(id)
{ {
Name = name; Name = name;
Location = location; Location = location;

View File

@@ -0,0 +1,7 @@
namespace Randall.Domain.Workplaces;
public static class WorkplaceFactory
{
public static Workplace Reconstitute(Guid id, string name, string location, bool isActive) =>
new(id, name, location, isActive);
}

View File

@@ -6,7 +6,7 @@ namespace Randall.Infrastructure.Persistence.Mappers;
public static class UserMapper public static class UserMapper
{ {
public static User ToDomain(UserRecord record) => public static User ToDomain(UserRecord record) =>
User.Reconstitute( UserFactory.Reconstitute(
record.Id, record.Id,
record.Email, record.Email,
record.Name, record.Name,

View File

@@ -6,7 +6,7 @@ namespace Randall.Infrastructure.Persistence.Mappers;
public static class WorkplaceMapper public static class WorkplaceMapper
{ {
public static Workplace ToDomain(WorkplaceRecord record) => public static Workplace ToDomain(WorkplaceRecord record) =>
Workplace.Reconstitute( WorkplaceFactory.Reconstitute(
record.Id, record.Id,
record.Name, record.Name,
record.Location, record.Location,

View File

@@ -0,0 +1,61 @@
using Randall.Domain.Reservations;
namespace Randall.Domain.UnitTests.Reservations;
public class ReservationFactoryTests
{
private static readonly Guid Id = Guid.NewGuid();
private static readonly Guid WorkplaceId = Guid.NewGuid();
private const string Email = "jane@company.com";
private const string Name = "Jane Smith";
private static readonly DateOnly Date = new(2026, 6, 1);
private static readonly DateTime CreatedAt = new(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc);
[Fact]
public void Reconstitute_MapsAllFieldsExactly()
{
var reservation = ReservationFactory.Reconstitute(Id, WorkplaceId, Email, Name, Date, ReservationStatus.Active, CreatedAt);
Assert.Equal(Id, reservation.Id);
Assert.Equal(WorkplaceId, reservation.WorkplaceId);
Assert.Equal(Email, reservation.EmployeeEmail);
Assert.Equal(Name, reservation.EmployeeName);
Assert.Equal(Date, reservation.Date);
Assert.Equal(ReservationStatus.Active, reservation.Status);
Assert.Equal(CreatedAt, reservation.CreatedAt);
}
[Fact]
public void Reconstitute_PreservesProvidedId()
{
var reservation = ReservationFactory.Reconstitute(Id, WorkplaceId, Email, Name, Date, ReservationStatus.Active, CreatedAt);
Assert.Equal(Id, reservation.Id);
}
[Fact]
public void Reconstitute_CanRestoreActiveReservation()
{
var reservation = ReservationFactory.Reconstitute(Id, WorkplaceId, Email, Name, Date, ReservationStatus.Active, CreatedAt);
Assert.Equal(ReservationStatus.Active, reservation.Status);
}
[Fact]
public void Reconstitute_CanRestoreCancelledReservation()
{
var reservation = ReservationFactory.Reconstitute(Id, WorkplaceId, Email, Name, Date, ReservationStatus.Cancelled, CreatedAt);
Assert.Equal(ReservationStatus.Cancelled, reservation.Status);
}
[Fact]
public void Reconstitute_CanRestorePastReservation()
{
var pastDate = new DateOnly(2020, 1, 1);
var reservation = ReservationFactory.Reconstitute(Id, WorkplaceId, Email, Name, pastDate, ReservationStatus.Active, CreatedAt);
Assert.Equal(pastDate, reservation.Date);
}
}

View File

@@ -0,0 +1,58 @@
using Randall.Domain.Users;
namespace Randall.Domain.UnitTests.Users;
public class UserFactoryTests
{
private static readonly Guid Id = Guid.NewGuid();
private const string Email = "jane@company.com";
private const string Name = "Jane Smith";
private const string PasswordHash = "hashed-password";
[Fact]
public void Reconstitute_MapsAllFieldsExactly()
{
var user = UserFactory.Reconstitute(Id, Email, Name, PasswordHash, isApproved: true, isAdmin: false);
Assert.Equal(Id, user.Id);
Assert.Equal(Email, user.Email);
Assert.Equal(Name, user.Name);
Assert.Equal(PasswordHash, user.PasswordHash);
Assert.True(user.IsApproved);
Assert.False(user.IsAdmin);
}
[Fact]
public void Reconstitute_PreservesProvidedId()
{
var user = UserFactory.Reconstitute(Id, Email, Name, PasswordHash, isApproved: false, isAdmin: false);
Assert.Equal(Id, user.Id);
}
[Fact]
public void Reconstitute_CanRestoreApprovedNonAdminUser()
{
var user = UserFactory.Reconstitute(Id, Email, Name, PasswordHash, isApproved: true, isAdmin: false);
Assert.True(user.IsApproved);
Assert.False(user.IsAdmin);
}
[Fact]
public void Reconstitute_CanRestoreUnapprovedUser()
{
var user = UserFactory.Reconstitute(Id, Email, Name, PasswordHash, isApproved: false, isAdmin: false);
Assert.False(user.IsApproved);
}
[Fact]
public void Reconstitute_CanRestoreAdminUser()
{
var user = UserFactory.Reconstitute(Id, Email, Name, PasswordHash, isApproved: true, isAdmin: true);
Assert.True(user.IsAdmin);
Assert.True(user.IsApproved);
}
}

View File

@@ -0,0 +1,45 @@
using Randall.Domain.Workplaces;
namespace Randall.Domain.UnitTests.Workplaces;
public class WorkplaceFactoryTests
{
private static readonly Guid Id = Guid.NewGuid();
private const string Name = "D13";
private const string Location = "Pod A";
[Fact]
public void Reconstitute_MapsAllFieldsExactly()
{
var workplace = WorkplaceFactory.Reconstitute(Id, Name, Location, isActive: true);
Assert.Equal(Id, workplace.Id);
Assert.Equal(Name, workplace.Name);
Assert.Equal(Location, workplace.Location);
Assert.True(workplace.IsActive);
}
[Fact]
public void Reconstitute_PreservesProvidedId()
{
var workplace = WorkplaceFactory.Reconstitute(Id, Name, Location, isActive: true);
Assert.Equal(Id, workplace.Id);
}
[Fact]
public void Reconstitute_CanRestoreActiveWorkplace()
{
var workplace = WorkplaceFactory.Reconstitute(Id, Name, Location, isActive: true);
Assert.True(workplace.IsActive);
}
[Fact]
public void Reconstitute_CanRestoreInactiveWorkplace()
{
var workplace = WorkplaceFactory.Reconstitute(Id, Name, Location, isActive: false);
Assert.False(workplace.IsActive);
}
}