Skip to content

Commit

Permalink
Consumer API: Decomposing a relationship as second Participant (#661)
Browse files Browse the repository at this point in the history
* feat: add new endpoint to RelationshipsController

* feat: implement Decompose()

* feat: add new handler

* feat: implement handler

* chore: rename audit log reason

* chore: fix formatting issues

* wip

* tests: add handler tests

* chore: remove empty lines, rename various items

* refactor: various smaller improvements

* feat: improve Decompose()

* test: add tests for second participant

* refactor: improve code structure

* chore: remove comments

* chore: rename test

* chore: remove brackets

* chore: rename method

* refactor: improve code structure of Decompose()

* chore: fix formatting

* format: improve code structure of Decompose()

* refactor: improve code structure of EnsureStatus() and Decompose()
  • Loading branch information
NikolaDmitrasinovic authored May 21, 2024
1 parent 201add0 commit 6519ae5
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ private static string ToDtoStringInternal(this RelationshipStatus status)
RelationshipStatus.Revoked => "Revoked",
RelationshipStatus.Terminated => "Terminated",
RelationshipStatus.DeletionProposed => "DeletionProposed",
RelationshipStatus.ReadyForDeletion => "ReadyForDeletion",
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ public void Accept(IdentityAddress activeIdentity, DeviceId activeDevice, byte[]
AuditLog.Add(auditLogEntry);
}

private void EnsureStatus(RelationshipStatus status)
private void EnsureStatus(params RelationshipStatus[] statuses)
{
if (Status != status)
throw new DomainException(DomainErrors.RelationshipIsNotInCorrectStatus(status));
if (!statuses.Contains(Status))
throw new DomainException(DomainErrors.RelationshipIsNotInCorrectStatus(statuses));
}

private void EnsureRelationshipRequestIsAddressedToSelf(IdentityAddress activeIdentity)
Expand Down Expand Up @@ -254,24 +254,52 @@ public void Decompose(IdentityAddress activeIdentity, DeviceId activeDevice)
{
EnsureHasParticipant(activeIdentity);
EnsureRelationshipNotDecomposedBy(activeIdentity);
EnsureStatus(RelationshipStatus.Terminated, RelationshipStatus.DeletionProposed);

if (Status == RelationshipStatus.Terminated)
DecomposeAsFirstParticipant(activeIdentity, activeDevice);
else
DecomposeAsSecondParticipant(activeIdentity, activeDevice);
}

private void DecomposeAsFirstParticipant(IdentityAddress activeIdentity, DeviceId activeDevice)
{
EnsureStatus(RelationshipStatus.Terminated);

Status = RelationshipStatus.DeletionProposed;

var auditLogEntry = new RelationshipAuditLogEntry(
RelationshipAuditLogEntryReason.Decomposition,
RelationshipStatus.Terminated,
RelationshipStatus.DeletionProposed,
activeIdentity,
activeDevice
);
AuditLog.Add(auditLogEntry);

if (From == activeIdentity)
FromHasDecomposed = true;

if (To == activeIdentity)
else
ToHasDecomposed = true;
}

Status = RelationshipStatus.DeletionProposed;
private void DecomposeAsSecondParticipant(IdentityAddress activeIdentity, DeviceId activeDevice)
{
EnsureStatus(RelationshipStatus.DeletionProposed);

Status = RelationshipStatus.ReadyForDeletion;

var auditLogEntry = new RelationshipAuditLogEntry(
RelationshipAuditLogEntryReason.Decomposed,
RelationshipStatus.Terminated,
RelationshipAuditLogEntryReason.Decomposition,
RelationshipStatus.DeletionProposed,
RelationshipStatus.ReadyForDeletion,
activeIdentity,
activeDevice
);
AuditLog.Add(auditLogEntry);

FromHasDecomposed = true;
ToHasDecomposed = true;
}

private void EnsureRelationshipNotDecomposedBy(IdentityAddress activeIdentity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public enum RelationshipAuditLogEntryReason
AcceptanceOfReactivation = 6,
RejectionOfReactivation = 7,
RevocationOfReactivation = 8,
Decomposed = 9
Decomposition = 9
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public enum RelationshipStatus
Rejected = 30,
Revoked = 40,
Terminated = 50,
DeletionProposed = 60
DeletionProposed = 60,
ReadyForDeletion = 70
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public static DomainError CannotRevokeRelationshipRequestNotCreatedByYourself()
"You cannot revoke a relationship request that was not created by yourself.");
}

public static DomainError RelationshipIsNotInCorrectStatus(RelationshipStatus expectedStatus)
public static DomainError RelationshipIsNotInCorrectStatus(RelationshipStatus[] expectedStatuses)
{
return new DomainError("error.platform.validation.relationshipRequest.relationshipIsNotInCorrectStatus",
$"The relationship has to be in status '{expectedStatus}' to perform this action.");
$"The relationship has to be in status '{string.Join(" or ", expectedStatuses)}' to perform this action.");
}

public static DomainError RelationshipToTargetAlreadyExists(string targetIdentity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task Returns_the_updated_relationship()
A.CallTo(() => fakeUserContext.GetAddress()).Returns(activeIdentity);
A.CallTo(() => fakeUserContext.GetDeviceId()).Returns(activeDevice);

var handler = CreateHandler(fakeUserContext, fakeRelationshipsRepository);
var handler = CreateHandler(fakeRelationshipsRepository, fakeUserContext);

// Act
var response = await handler.Handle(new DecomposeRelationshipCommand
Expand Down Expand Up @@ -61,7 +61,7 @@ public async Task Saves_the_updated_relationship()
A.CallTo(() => fakeUserContext.GetAddress()).Returns(activeIdentity);
A.CallTo(() => fakeUserContext.GetDeviceId()).Returns(activeDevice);

var handler = CreateHandler(fakeUserContext, mockRelationshipsRepository);
var handler = CreateHandler(mockRelationshipsRepository, fakeUserContext);

// Act
await handler.Handle(new DecomposeRelationshipCommand
Expand All @@ -78,7 +78,7 @@ await handler.Handle(new DecomposeRelationshipCommand
}

[Fact]
public async Task Publishes_RelationshipStatusChangedIntegrationEvent()
public async Task Publishes_RelationshipStatusChangedDomainEvent()
{
// Arrange
var activeIdentity = TestDataGenerator.CreateRandomIdentityAddress();
Expand All @@ -94,7 +94,7 @@ public async Task Publishes_RelationshipStatusChangedIntegrationEvent()

var mockEventBus = A.Fake<IEventBus>();

var handler = CreateHandler(fakeUserContext, fakeRelationshipsRepository, mockEventBus);
var handler = CreateHandler(fakeRelationshipsRepository, fakeUserContext, mockEventBus);

// Act
await handler.Handle(new DecomposeRelationshipCommand
Expand All @@ -113,7 +113,7 @@ await handler.Handle(new DecomposeRelationshipCommand
.MustHaveHappenedOnceExactly();
}

private static Handler CreateHandler(IUserContext userContext, IRelationshipsRepository relationshipsRepository, IEventBus? eventBus = null)
private static Handler CreateHandler(IRelationshipsRepository relationshipsRepository, IUserContext userContext, IEventBus? eventBus = null)
{
eventBus ??= A.Dummy<IEventBus>();
return new Handler(relationshipsRepository, userContext, eventBus);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,13 @@ public static Relationship CreateDecomposedRelationship(IdentityAddress? from =
relationship.Decompose(decomposedBy, DEVICE_1);
return relationship;
}

public static Relationship CreateRelationshipDecomposedByFrom(IdentityAddress from, IdentityAddress to)
{
var relationship = CreateTerminatedRelationship(from, to);

relationship.Decompose(from, DEVICE_1);

return relationship;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Backbone.Modules.Relationships.Domain.Tests.Tests.Aggregates.Relations
public class DecomposeRelationshipTests
{
[Fact]
public void Decomposing_relationship_transitions_relationship_to_status_deletion_proposed()
public void Decomposing_as_firstParticipant_transitions_relationship_to_status_DeletionProposed()
{
// Arrange
var relationship = CreateTerminatedRelationship(IDENTITY_1, IDENTITY_2);
Expand All @@ -26,7 +26,36 @@ public void Decomposing_relationship_transitions_relationship_to_status_deletion
}

[Fact]
public void Can_only_decompose_when_relationship_is_in_status_terminated()
public void Decomposing_as_second_participant_transitions_relationship_to_status_ReadyForDeletion()
{
// Arrange
var relationship = CreateRelationshipDecomposedByFrom(IDENTITY_1, IDENTITY_2);

// Act
relationship.Decompose(IDENTITY_2, DEVICE_2);

// Assert
relationship.Status.Should().Be(RelationshipStatus.ReadyForDeletion);
}

[Fact]
public void Can_only_decompose_when_relationship_is_in_status_Terminated_or_DeletionProposed()
{
// Arrange
var relationship = CreatePendingRelationship(IDENTITY_1, IDENTITY_2);

// Act
var acting = () => relationship.Decompose(IDENTITY_1, DEVICE_1);

// Assert
acting.Should().Throw<DomainException>().WithError(
"error.platform.validation.relationshipRequest.relationshipIsNotInCorrectStatus",
nameof(RelationshipStatus.DeletionProposed)
);
}

[Fact]
public void Can_only_decompose_as_firstParticipant_when_relationship_is_in_status_Terminated()
{
// Arrange
var relationship = CreatePendingRelationship(IDENTITY_1, IDENTITY_2);
Expand All @@ -42,31 +71,56 @@ public void Can_only_decompose_when_relationship_is_in_status_terminated()
}

[Fact]
public void Decomposing_relationship_creates_an_audit_log_entry()
public void Decomposing_as_firstParticipant_creates_an_AuditLog_entry()
{
// Arrange
SystemTime.Set("2000-01-01");
var relationship = CreateTerminatedRelationship(IDENTITY_1, IDENTITY_2);

// Act
relationship.Decompose(IDENTITY_2, DEVICE_2);
relationship.Decompose(IDENTITY_1, DEVICE_1);

// Assert
relationship.AuditLog.Should().HaveCount(4);

var auditLogEntry = relationship.AuditLog.Last();

auditLogEntry.Id.Should().NotBeNull();
auditLogEntry.Reason.Should().Be(RelationshipAuditLogEntryReason.Decomposed);
auditLogEntry.Reason.Should().Be(RelationshipAuditLogEntryReason.Decomposition);
auditLogEntry.OldStatus.Should().Be(RelationshipStatus.Terminated);
auditLogEntry.NewStatus.Should().Be(RelationshipStatus.DeletionProposed);
auditLogEntry.CreatedBy.Should().Be(IDENTITY_1);
auditLogEntry.CreatedByDevice.Should().Be(DEVICE_1);
auditLogEntry.CreatedAt.Should().Be(DateTime.Parse("2000-01-01"));
}

[Fact]
public void Decomposing_as_secondParticipant_creates_an_AuditLog_entry()
{
// Arrange
SystemTime.Set("2000-01-01");

var relationship = CreateRelationshipDecomposedByFrom(IDENTITY_1, IDENTITY_2);

// Act
relationship.Decompose(IDENTITY_2, DEVICE_2);

// Assert
relationship.AuditLog.Should().HaveCount(5); // AuditLog(Creation->Acceptance->Termination->Decomposition->Decomposition)

var auditLogEntry = relationship.AuditLog.Last();

auditLogEntry.Id.Should().NotBeNull();
auditLogEntry.Reason.Should().Be(RelationshipAuditLogEntryReason.Decomposition);
auditLogEntry.OldStatus.Should().Be(RelationshipStatus.DeletionProposed);
auditLogEntry.NewStatus.Should().Be(RelationshipStatus.ReadyForDeletion);
auditLogEntry.CreatedBy.Should().Be(IDENTITY_2);
auditLogEntry.CreatedByDevice.Should().Be(DEVICE_2);
auditLogEntry.CreatedAt.Should().Be(DateTime.Parse("2000-01-01"));
}

[Fact]
public void Only_a_identity_belonging_to_the_relationship_can_decompose_it()
public void Identity_must_belong_to_relationship_to_decompose_it()
{
// Arrange
var relationship = CreateTerminatedRelationship(IDENTITY_1, IDENTITY_2);
Expand Down

0 comments on commit 6519ae5

Please sign in to comment.