Skip to content

Commit

Permalink
Fixing the default organization for a machine
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jun 10, 2024
1 parent 1d7c89c commit afc12ba
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 52 deletions.
25 changes: 18 additions & 7 deletions src/Domain.Shared/Feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,26 @@ public static ValueObjectFactory<Feature> Rehydrate()
[SkipImmutabilityCheck]
public FeatureLevel AsLevel()
{
var knownPlatform = PlatformFeatures.FindFeatureByName(Identifier);
if (knownPlatform.Exists())
return Identifier.ToFeatureLevel();
}
}

public static class FeatureExtensions
{
public static FeatureLevel ToFeatureLevel(this string name)
{
var platform = PlatformFeatures.FindFeatureByName(name);
if (platform.Exists())
{
return platform;
}

var tenant = TenantFeatures.FindFeatureByName(name);
if (tenant.Exists())
{
return knownPlatform;
return tenant;
}

var knownTenant = TenantFeatures.FindFeatureByName(Identifier);
return knownTenant.Exists()
? knownTenant
: new FeatureLevel(Identifier);
return new FeatureLevel(name);
}
}
10 changes: 5 additions & 5 deletions src/Domain.Shared/Features.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public sealed class Features : SingleValueObjectBase<Features, List<Feature>>

public static Result<Features, Error> Create(string feature)
{
return Create(new FeatureLevel(feature));
return Create(feature.ToFeatureLevel());
}

public static Result<Features, Error> Create(FeatureLevel feature)
Expand All @@ -29,7 +29,7 @@ public static Result<Features, Error> Create(FeatureLevel feature)

public static Result<Features, Error> Create(params string[] features)
{
return Create(features.Select(feature => new FeatureLevel(feature)).ToArray());
return Create(features.Select(feature => feature.ToFeatureLevel()).ToArray());
}

public static Result<Features, Error> Create(params FeatureLevel[] features)
Expand Down Expand Up @@ -72,7 +72,7 @@ public static ValueObjectFactory<Features> Rehydrate()

public Result<Features, Error> Add(string feature)
{
var featureLevel = Feature.Create(new FeatureLevel(feature));
var featureLevel = Feature.Create(feature.ToFeatureLevel());
if (featureLevel.IsFailure)
{
return featureLevel.Error;
Expand Down Expand Up @@ -142,7 +142,7 @@ public bool HasNone()

public Features Remove(string feature)
{
return Remove(new FeatureLevel(feature));
return Remove(feature.ToFeatureLevel());
}

public Features Remove(FeatureLevel feature)
Expand Down Expand Up @@ -187,4 +187,4 @@ private Features Remove(Feature feature)

return new Features(features);
}
}
}
25 changes: 18 additions & 7 deletions src/Domain.Shared/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,26 @@ public static ValueObjectFactory<Role> Rehydrate()
[SkipImmutabilityCheck]
public RoleLevel AsLevel()
{
var knownPlatform = PlatformRoles.FindRoleByName(Identifier);
if (knownPlatform.Exists())
return Identifier.ToRoleLevel();
}
}

public static class RoleExtensions
{
public static RoleLevel ToRoleLevel(this string name)
{
var platform = PlatformRoles.FindRoleByName(name);
if (platform.Exists())
{
return platform;
}

var tenant = TenantRoles.FindRoleByName(name);
if (tenant.Exists())
{
return knownPlatform;
return tenant;
}

var knownTenant = TenantRoles.FindRoleByName(Identifier);
return knownTenant.Exists()
? knownTenant
: new RoleLevel(Identifier);
return new RoleLevel(name);
}
}
8 changes: 4 additions & 4 deletions src/Domain.Shared/Roles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public sealed class Roles : SingleValueObjectBase<Roles, List<Role>>

public static Result<Roles, Error> Create(string role)
{
return Create(new RoleLevel(role));
return Create(role.ToRoleLevel());
}

public static Result<Roles, Error> Create(RoleLevel role)
Expand All @@ -29,7 +29,7 @@ public static Result<Roles, Error> Create(RoleLevel role)

public static Result<Roles, Error> Create(params string[] roles)
{
return Create(roles.Select(role => new RoleLevel(role)).ToArray());
return Create(roles.Select(role => role.ToRoleLevel()).ToArray());
}

public static Result<Roles, Error> Create(params RoleLevel[] roles)
Expand Down Expand Up @@ -72,7 +72,7 @@ public static ValueObjectFactory<Roles> Rehydrate()

public Result<Roles, Error> Add(string role)
{
var rol = Role.Create(new RoleLevel(role));
var rol = Role.Create(role.ToRoleLevel());
if (rol.IsFailure)
{
return rol.Error;
Expand Down Expand Up @@ -142,7 +142,7 @@ public bool HasRole(RoleLevel role)

public Roles Remove(string role)
{
return Remove(new RoleLevel(role));
return Remove(role.ToRoleLevel());
}

public Roles Remove(RoleLevel role)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public EndUsersApplicationDomainEventHandlersSpec()
_endUserRepository = new Mock<IEndUserRepository>();
_endUserRepository.Setup(rep => rep.SaveAsync(It.IsAny<EndUserRoot>(), It.IsAny<CancellationToken>()))
.Returns((EndUserRoot root, CancellationToken _) => Task.FromResult<Result<EndUserRoot, Error>>(root));
_endUserRepository.Setup(rep =>
rep.SaveAsync(It.IsAny<EndUserRoot>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns((EndUserRoot root, bool _, CancellationToken _) =>
Task.FromResult<Result<EndUserRoot, Error>>(root));
var invitationRepository = new Mock<IInvitationRepository>();
var userProfilesService = new Mock<IUserProfilesService>();
var notificationsService = new Mock<IUserNotificationsService>();
Expand All @@ -76,7 +80,7 @@ public async Task WhenHandleOrganizationCreatedAsyncAndUserNoExist_ThenReturnsEr
}

[Fact]
public async Task HandleOrganizationCreatedAsync_ThenAddsMembership()
public async Task HandleOrganizationCreatedAsyncForPerson_ThenAddsMembership()
{
var user = EndUserRoot.Create(_recorder.Object, _idFactory.Object, UserClassification.Person).Value;
user.Register(Roles.Create(PlatformRoles.Standard).Value, Features.Create(PlatformFeatures.Basic).Value,
Expand All @@ -91,10 +95,41 @@ public async Task HandleOrganizationCreatedAsync_ThenAddsMembership()

result.Should().BeSuccess();
_endUserRepository.Verify(rep => rep.SaveAsync(It.Is<EndUserRoot>(eu =>
eu.Memberships[0].IsDefault
eu.Memberships.Count == 1
&& eu.Memberships[0].IsDefault
&& eu.Memberships[0].OrganizationId == "anorganizationid".ToId()
&& eu.Memberships[0].Roles.HasRole(TenantRoles.Member)
&& eu.Memberships[0].Features.HasFeature(TenantFeatures.Basic)
), It.IsAny<bool>(), It.IsAny<CancellationToken>()));
}

[Fact]
public async Task HandleOrganizationCreatedAsyncForAnInvitedMachine_ThenAddsMemberships()
{
var machine = EndUserRoot.Create(_recorder.Object, _idFactory.Object, UserClassification.Machine).Value;
machine.Register(Roles.Create(PlatformRoles.Standard).Value, Features.Create(PlatformFeatures.Basic).Value,
EndUserProfile.Create("afirstname").Value, EmailAddress.Create("[email protected]").Value);
machine.AddMembership(machine, OrganizationOwnership.Shared, "anorganizationid2".ToId(),
Roles.Create(TenantRoles.Member).Value, Features.Create(TenantFeatures.Basic).Value);
_endUserRepository.Setup(rep => rep.LoadAsync(It.IsAny<Identifier>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(machine);
var domainEvent = Events.Created("anorganizationid1".ToId(), OrganizationOwnership.Shared,
"auserid".ToId(), DisplayName.Create("adisplayname").Value);

var result =
await _application.HandleOrganizationCreatedAsync(_caller.Object, domainEvent, CancellationToken.None);

result.Should().BeSuccess();
_endUserRepository.Verify(rep => rep.SaveAsync(It.Is<EndUserRoot>(eu =>
eu.Memberships.Count == 2
&& eu.Memberships[0].IsDefault
&& eu.Memberships[0].OrganizationId == "anorganizationid2".ToId()
&& eu.Memberships[0].Roles.HasRole(TenantRoles.Member)
&& eu.Memberships[0].Features.HasFeature(TenantFeatures.Basic)
&& !eu.Memberships[1].IsDefault
&& eu.Memberships[1].OrganizationId == "anorganizationid1".ToId()
&& eu.Memberships[1].Roles == Roles.Create(TenantRoles.BillingAdmin).Value
&& eu.Memberships[1].Features == Features.Create(TenantFeatures.PaidTrial).Value
), It.IsAny<CancellationToken>()));
}

Expand Down
17 changes: 15 additions & 2 deletions src/EndUsersApplication.UnitTests/EndUsersApplicationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,15 @@ public async Task WhenRegisterMachineAsyncByAnonymousUser_ThenRegistersWithNoFea
result.Value.Classification.Should().Be(EndUserClassification.Machine);
result.Value.Roles.Should().OnlyContain(role => role == PlatformRoles.Standard.Name);
result.Value.Features.Should().OnlyContain(feat => feat == PlatformFeatures.Basic.Name);
_endUserRepository.Verify(rep => rep.SaveAsync(It.Is<EndUserRoot>(eur =>
eur.Memberships.Count == 0
&& eur.Roles == Roles.Create(PlatformRoles.Standard).Value
&& eur.Features == Features.Create(PlatformFeatures.Basic).Value
), It.IsAny<bool>(), It.IsAny<CancellationToken>()));
}

[Fact]
public async Task WhenRegisterMachineAsyncByAuthenticatedUser_ThenRegistersWithBasicFeatures()
public async Task WhenRegisterMachineAsyncByAuthenticatedUser_ThenRegistersAndInvites()
{
_caller.Setup(cc => cc.IsAuthenticated)
.Returns(true);
Expand Down Expand Up @@ -520,7 +525,15 @@ public async Task WhenRegisterMachineAsyncByAuthenticatedUser_ThenRegistersWithB
result.Value.Status.Should().Be(EndUserStatus.Registered);
result.Value.Classification.Should().Be(EndUserClassification.Machine);
result.Value.Roles.Should().OnlyContain(role => role == PlatformRoles.Standard.Name);
result.Value.Features.Should().ContainInOrder(TenantFeatures.PaidTrial.Name, TenantFeatures.Basic.Name);
result.Value.Features.Should().ContainInOrder(PlatformFeatures.PaidTrial.Name, PlatformFeatures.Basic.Name);
_endUserRepository.Verify(rep => rep.SaveAsync(It.Is<EndUserRoot>(eur =>
eur.Memberships.Count == 1
&& eur.Memberships[0].OrganizationId == "anotherorganizationid".ToId()
&& eur.Memberships[0].Roles == Roles.Create(TenantRoles.Member).Value
&& eur.Memberships[0].Features == Features.Create(TenantFeatures.PaidTrial, TenantFeatures.Basic).Value
&& eur.Roles == Roles.Create(PlatformRoles.Standard).Value
&& eur.Features == Features.Create(PlatformFeatures.PaidTrial, PlatformFeatures.Basic).Value
), It.IsAny<CancellationToken>()));
}

#if TESTINGONLY
Expand Down
53 changes: 34 additions & 19 deletions src/EndUsersApplication/EndUsersApplication.DomainEventHandlers.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using Application.Common.Extensions;
using Application.Interfaces;
using Application.Resources.Shared;
using Common;
using Common.Extensions;
using Domain.Common.ValueObjects;
using Domain.Events.Shared.Organizations;
using Domain.Shared;
using Domain.Shared.EndUsers;
using Domain.Shared.Organizations;
using EndUsersDomain;
using Membership = Application.Resources.Shared.Membership;

namespace EndUsersApplication;

Expand All @@ -18,7 +16,7 @@ public async Task<Result<Error>> HandleOrganizationCreatedAsync(ICallerContext c
CancellationToken cancellationToken)
{
var ownership = domainEvent.Ownership.ToEnumOrDefault(OrganizationOwnership.Shared);
var membership = await CreateMembershipAsync(caller, domainEvent.CreatedById.ToId(), domainEvent.RootId.ToId(),
var membership = await CreateMembershipsAsync(caller, domainEvent.CreatedById.ToId(), domainEvent.RootId.ToId(),
ownership, cancellationToken);
if (membership.IsFailure)
{
Expand Down Expand Up @@ -100,51 +98,68 @@ private async Task<Result<Error>> RemoveMembershipFromDeletedOrganizationAsync(I
return Result.Ok;
}

private async Task<Result<Membership, Error>> CreateMembershipAsync(ICallerContext caller,
private async Task<Result<Error>> CreateMembershipsAsync(ICallerContext caller,
Identifier createdById, Identifier organizationId, OrganizationOwnership ownership,
CancellationToken cancellationToken)
{
var retrievedInviter = await _endUserRepository.LoadAsync(createdById, cancellationToken);
if (retrievedInviter.IsFailure)
var retrievedOwner = await _endUserRepository.LoadAsync(createdById, cancellationToken);
if (retrievedOwner.IsFailure)
{
return retrievedInviter.Error;
return retrievedOwner.Error;
}

var inviter = retrievedInviter.Value;
var owner = retrievedOwner.Value;
var useCase = ownership switch
{
OrganizationOwnership.Shared => RolesAndFeaturesUseCase.CreatingOrg,
OrganizationOwnership.Personal => inviter.Classification == UserClassification.Person
OrganizationOwnership.Personal => owner.IsPerson
? RolesAndFeaturesUseCase.CreatingPerson
: RolesAndFeaturesUseCase.CreatingMachine,
_ => RolesAndFeaturesUseCase.CreatingOrg
};
var (_, _, tenantRoles, tenantFeatures) =
EndUserRoot.GetInitialRolesAndFeatures(useCase, caller.IsAuthenticated);
var inviterOwnership = ownership.ToEnumOrDefault(Domain.Shared.Organizations.OrganizationOwnership.Shared);
var membered = inviter.AddMembership(inviter, inviterOwnership, organizationId, tenantRoles, tenantFeatures);
var membered = owner.AddMembership(owner, ownership, organizationId, tenantRoles, tenantFeatures);
if (membered.IsFailure)
{
return membered.Error;
}

var saved = await _endUserRepository.SaveAsync(inviter, cancellationToken);
var saved = await _endUserRepository.SaveAsync(owner, true, cancellationToken);
if (saved.IsFailure)
{
return saved.Error;
}

inviter = saved.Value;
owner = saved.Value;
_recorder.TraceInformation(caller.ToCall(), "EndUser {Id} has become a member of organization {Organization}",
inviter.Id, organizationId);
owner.Id, organizationId);

var membership = inviter.FindMembership(organizationId);
if (!membership.HasValue)
if (owner.IsMachine)
{
return Error.EntityNotFound(Resources.EndUsersApplication_MembershipNotFound);
var previousMembership = owner.Memberships.FirstOrDefault(m => m.OrganizationId != organizationId);
if (previousMembership.Exists())
{
var changed = owner.ChangeDefaultMembership(previousMembership.OrganizationId);
if (changed.IsFailure)
{
return changed.Error;
}

saved = await _endUserRepository.SaveAsync(owner, cancellationToken);
if (saved.IsFailure)
{
return saved.Error;
}

owner = saved.Value;
_recorder.TraceInformation(caller.ToCall(),
"Machine {Id} has become a member of organization {Organization}",
owner.Id, previousMembership.OrganizationId);
}
}

return membership.Value.ToMembership();
return Result.Ok;
}

private async Task<Result<Error>> AssignTenantRolesAsync(ICallerContext caller, Identifier assignerId,
Expand Down
6 changes: 3 additions & 3 deletions src/EndUsersApplication/EndUsersApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public async Task<Result<EndUser, Error>> RegisterMachineAsync(ICallerContext ca
{
return retrievedAdder.Error;
}

var adder = retrievedAdder.Value;
var adderDefaultMembership = adder.DefaultMembership;
if (adderDefaultMembership.IsShared)
Expand All @@ -173,13 +173,13 @@ public async Task<Result<EndUser, Error>> RegisterMachineAsync(ICallerContext ca
{
return adderEnrolled.Error;
}

saved = await _endUserRepository.SaveAsync(machine, cancellationToken);
if (saved.IsFailure)
{
return saved.Error;
}

machine = saved.Value;
_recorder.TraceInformation(caller.ToCall(),
"Machine {Id} has become a member of {User} organization {Organization}",
Expand Down
2 changes: 1 addition & 1 deletion src/EndUsersDomain/EndUserRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private EndUserRoot(IRecorder recorder, IIdentifierFactory idFactory, ISingleVal

public GuestInvitation GuestInvitation { get; private set; } = GuestInvitation.Empty;

private bool IsMachine => Classification == UserClassification.Machine;
public bool IsMachine => Classification == UserClassification.Machine;

public bool IsPerson => Classification == UserClassification.Person;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ public async Task<Result<Error>> HandleEndUserRegisteredAsync(ICallerContext cal
var name =
$"{domainEvent.UserProfile.FirstName}{(domainEvent.UserProfile.LastName.HasValue() ? " " + domainEvent.UserProfile.LastName : string.Empty)}";
var organization = await CreateOrganizationInternalAsync(caller, domainEvent.RootId.ToId(),
domainEvent.Classification, name,
OrganizationOwnership.Personal, cancellationToken);
domainEvent.Classification, name, OrganizationOwnership.Personal, cancellationToken);
if (organization.IsFailure)
{
return organization.Error;
Expand Down

0 comments on commit afc12ba

Please sign in to comment.