diff --git a/test/Core.Test/Auth/UserFeatures/EmergencyAccess/DeleteEmergencyAccessCommandTests.cs b/test/Core.Test/Auth/UserFeatures/EmergencyAccess/DeleteEmergencyAccessCommandTests.cs index 0b47b97472..88d6a2b404 100644 --- a/test/Core.Test/Auth/UserFeatures/EmergencyAccess/DeleteEmergencyAccessCommandTests.cs +++ b/test/Core.Test/Auth/UserFeatures/EmergencyAccess/DeleteEmergencyAccessCommandTests.cs @@ -1,5 +1,4 @@ -using Bit.Core.Auth.Enums; -using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.UserFeatures.EmergencyAccess.Commands; using Bit.Core.Auth.UserFeatures.EmergencyAccess.Mail; using Bit.Core.Exceptions; @@ -15,6 +14,10 @@ namespace Bit.Core.Test.Auth.UserFeatures.EmergencyAccess; [SutProviderCustomize] public class DeleteEmergencyAccessCommandTests { + /// + /// Verifies that attempting to delete a non-existent emergency access record + /// throws a and does not call delete or send email. + /// [Theory, BitAutoData] public async Task DeleteByIdGrantorIdAsync_EmergencyAccessNotFound_ThrowsBadRequest( SutProvider sutProvider, @@ -37,41 +40,36 @@ public class DeleteEmergencyAccessCommandTests .SendEmail(default); } + /// + /// Verifies that a valid delete request successfully deletes the emergency access record, + /// returns the deleted details, and sends a notification email to the grantor. + /// [Theory, BitAutoData] public async Task DeleteByIdGrantorIdAsync_ValidRequest_DeletesAndReturnsDetails( SutProvider sutProvider, - Guid emergencyAccessId, - Guid grantorId, - Guid granteeId) + EmergencyAccessDetails emergencyAccessDetails) { - var emergencyAccessDetails = new EmergencyAccessDetails - { - Id = emergencyAccessId, - GrantorId = grantorId, - GranteeId = granteeId, - GranteeEmail = "grantee@test.dev", - GrantorEmail = "grantor@test.dev", - Status = EmergencyAccessStatusType.Confirmed, - Type = EmergencyAccessType.View - }; - sutProvider.GetDependency() - .GetDetailsByIdGrantorIdAsync(emergencyAccessId, grantorId) + .GetDetailsByIdGrantorIdAsync(emergencyAccessDetails.Id, emergencyAccessDetails.GrantorId) .Returns(emergencyAccessDetails); - var result = await sutProvider.Sut.DeleteByIdGrantorIdAsync(emergencyAccessId, grantorId); + var result = await sutProvider.Sut.DeleteByIdGrantorIdAsync(emergencyAccessDetails.Id, emergencyAccessDetails.GrantorId); Assert.NotNull(result); - Assert.Equal(emergencyAccessId, result.Id); - Assert.Equal(grantorId, result.GrantorId); + Assert.Equal(emergencyAccessDetails.Id, result.Id); + Assert.Equal(emergencyAccessDetails.GrantorId, result.GrantorId); await sutProvider.GetDependency() .Received(1) - .DeleteAsync(Arg.Is(ea => ea.Id == emergencyAccessId)); + .DeleteAsync(Arg.Is(ea => ea.Id == emergencyAccessDetails.Id)); await sutProvider.GetDependency() .Received(1) .SendEmail(Arg.Any()); } + /// + /// Verifies that when a grantor has no emergency access records, the method returns + /// an empty collection and does not attempt to delete or send email. + /// [Theory, BitAutoData] public async Task DeleteAllByGrantorIdAsync_NoEmergencyAccessRecords_ReturnsEmptyCollection( SutProvider sutProvider, @@ -79,7 +77,7 @@ public class DeleteEmergencyAccessCommandTests { sutProvider.GetDependency() .GetManyDetailsByGrantorIdAsync(grantorId) - .Returns(new List()); + .Returns([]); var result = await sutProvider.Sut.DeleteAllByGrantorIdAsync(grantorId); @@ -93,42 +91,22 @@ public class DeleteEmergencyAccessCommandTests .SendEmail(default); } + /// + /// Verifies that when a grantor has multiple emergency access records, all records are deleted, + /// the details are returned, and a single notification email is sent. + /// [Theory, BitAutoData] public async Task DeleteAllByGrantorIdAsync_MultipleRecords_DeletesAllAndReturnsDetails( SutProvider sutProvider, + EmergencyAccessDetails emergencyAccessDetails1, + EmergencyAccessDetails emergencyAccessDetails2, + EmergencyAccessDetails emergencyAccessDetails3, Guid grantorId) { - var emergencyAccessDetails1 = new EmergencyAccessDetails - { - Id = Guid.NewGuid(), - GrantorId = grantorId, - GranteeId = Guid.NewGuid(), - GranteeEmail = "grantee@test.dev", - GrantorEmail = "grantor@test.dev", - Status = EmergencyAccessStatusType.Confirmed, - Type = EmergencyAccessType.View - }; - - var emergencyAccessDetails2 = new EmergencyAccessDetails - { - Id = Guid.NewGuid(), - GrantorId = grantorId, - GranteeId = Guid.NewGuid(), - GranteeEmail = "grantee@test.dev", - GrantorEmail = "grantor@test.dev", - Status = EmergencyAccessStatusType.Invited, - Type = EmergencyAccessType.Takeover - }; - - var emergencyAccessDetails3 = new EmergencyAccessDetails - { - Id = Guid.NewGuid(), - GrantorId = grantorId, - GranteeId = Guid.NewGuid(), - GranteeEmail = "grantee@test.dev", - GrantorEmail = "grantor@test.dev", - Type = EmergencyAccessType.View - }; + // link all details to the same grantor + emergencyAccessDetails1.GrantorId = grantorId; + emergencyAccessDetails2.GrantorId = grantorId; + emergencyAccessDetails3.GrantorId = grantorId; var allDetails = new List { @@ -153,24 +131,16 @@ public class DeleteEmergencyAccessCommandTests .SendEmail(Arg.Any()); } + /// + /// Verifies that when a grantor has a single emergency access record, it is deleted, + /// the details are returned, and a notification email is sent. + /// [Theory, BitAutoData] public async Task DeleteAllByGrantorIdAsync_SingleRecord_DeletesAndReturnsDetails( SutProvider sutProvider, - Guid grantorId, - Guid granteeId) + EmergencyAccessDetails emergencyAccessDetails, + Guid grantorId) { - var emergencyAccessId = Guid.NewGuid(); - var emergencyAccessDetails = new EmergencyAccessDetails - { - Id = emergencyAccessId, - GrantorId = grantorId, - GranteeId = granteeId, - GranteeEmail = "grantee@test.dev", - GrantorEmail = "grantor@test.dev", - Status = EmergencyAccessStatusType.Confirmed, - Type = EmergencyAccessType.Takeover - }; - sutProvider.GetDependency() .GetManyDetailsByGrantorIdAsync(grantorId) .Returns([emergencyAccessDetails]); @@ -179,10 +149,103 @@ public class DeleteEmergencyAccessCommandTests Assert.NotNull(result); Assert.Single(result); - Assert.Equal(emergencyAccessId, result.First().Id); + Assert.Equal(emergencyAccessDetails.Id, result.First().Id); await sutProvider.GetDependency() .Received(1) - .DeleteAsync(Arg.Is(ea => ea.Id == emergencyAccessId)); + .DeleteAsync(Arg.Is(ea => ea.Id == emergencyAccessDetails.Id)); + await sutProvider.GetDependency() + .Received(1) + .SendEmail(Arg.Any()); + } + + /// + /// Verifies that when a grantee has no emergency access records, the method returns + /// an empty collection and does not attempt to delete or send email. + /// + [Theory, BitAutoData] + public async Task DeleteAllByGranteeIdAsync_NoEmergencyAccessRecords_ReturnsEmptyCollection( + SutProvider sutProvider, + Guid granteeId) + { + sutProvider.GetDependency() + .GetManyDetailsByGranteeIdAsync(granteeId) + .Returns([]); + + var result = await sutProvider.Sut.DeleteAllByGranteeIdAsync(granteeId); + + Assert.NotNull(result); + Assert.Empty(result); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .DeleteAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .SendEmail(default); + } + + /// + /// Verifies that when a grantee has a single emergency access record, it is deleted, + /// the details are returned, and a notification email is sent to the grantor. + /// + [Theory, BitAutoData] + public async Task DeleteAllByGranteeIdAsync_SingleRecord_DeletesAndReturnsDetails( + SutProvider sutProvider, + EmergencyAccessDetails emergencyAccessDetails, + Guid granteeId) + { + sutProvider.GetDependency() + .GetManyDetailsByGranteeIdAsync(granteeId) + .Returns([emergencyAccessDetails]); + + var result = await sutProvider.Sut.DeleteAllByGranteeIdAsync(granteeId); + + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal(emergencyAccessDetails.Id, result.First().Id); + await sutProvider.GetDependency() + .Received(1) + .DeleteAsync(Arg.Is(ea => ea.Id == emergencyAccessDetails.Id)); + await sutProvider.GetDependency() + .Received(1) + .SendEmail(Arg.Any()); + } + + /// + /// Verifies that when a grantee has multiple emergency access records from different grantors, + /// all records are deleted, the details are returned, and a single notification email is sent + /// to all affected grantors. + /// + [Theory, BitAutoData] + public async Task DeleteAllByGranteeIdAsync_MultipleRecords_DeletesAllAndReturnsDetails( + SutProvider sutProvider, + EmergencyAccessDetails emergencyAccessDetails1, + EmergencyAccessDetails emergencyAccessDetails2, + EmergencyAccessDetails emergencyAccessDetails3, + Guid granteeId) + { + // link all details to the same grantee + emergencyAccessDetails1.GranteeId = granteeId; + emergencyAccessDetails2.GranteeId = granteeId; + emergencyAccessDetails3.GranteeId = granteeId; + + var allDetails = new List + { + emergencyAccessDetails1, + emergencyAccessDetails2, + emergencyAccessDetails3 + }; + + sutProvider.GetDependency() + .GetManyDetailsByGranteeIdAsync(granteeId) + .Returns(allDetails); + + var result = await sutProvider.Sut.DeleteAllByGranteeIdAsync(granteeId); + + Assert.NotNull(result); + Assert.Equal(3, result.Count); + await sutProvider.GetDependency() + .Received(3) + .DeleteAsync(Arg.Any()); await sutProvider.GetDependency() .Received(1) .SendEmail(Arg.Any()); diff --git a/test/Core.Test/Auth/UserFeatures/EmergencyAccess/EmergencyAccessMailTests.cs b/test/Core.Test/Auth/UserFeatures/EmergencyAccess/EmergencyAccessMailTests.cs index 0a64c59d58..cee2f64fc4 100644 --- a/test/Core.Test/Auth/UserFeatures/EmergencyAccess/EmergencyAccessMailTests.cs +++ b/test/Core.Test/Auth/UserFeatures/EmergencyAccess/EmergencyAccessMailTests.cs @@ -26,7 +26,7 @@ public class EmergencyAccessMailTests [Theory, BitAutoData] public async Task SendEmergencyAccessRemoveGranteesEmail_SingleGrantee_Success( string grantorEmail, - string granteeName) + string granteeEmail) { // Arrange var logger = Substitute.For>(); @@ -41,7 +41,7 @@ public class EmergencyAccessMailTests ToEmails = [grantorEmail], View = new EmergencyAccessRemoveGranteesMailView { - RemovedGranteeEmails = [granteeName] + RemovedGranteeEmails = [granteeEmail] } }; @@ -58,8 +58,8 @@ public class EmergencyAccessMailTests Assert.Contains(grantorEmail, sentMessage.ToEmails); // Verify the content contains the grantee name - Assert.Contains(granteeName, sentMessage.TextContent); - Assert.Contains(granteeName, sentMessage.HtmlContent); + Assert.Contains(granteeEmail, sentMessage.TextContent); + Assert.Contains(granteeEmail, sentMessage.HtmlContent); } /// @@ -77,14 +77,14 @@ public class EmergencyAccessMailTests new HandlebarMailRenderer(logger, globalSettings), deliveryService); - var granteeNames = new[] { "Alice", "Bob", "Carol" }; + var granteeEmails = new[] { "Alice@test.dev", "Bob@test.dev", "Carol@test.dev" }; var mail = new EmergencyAccessRemoveGranteesMail { ToEmails = [grantorEmail], View = new EmergencyAccessRemoveGranteesMailView { - RemovedGranteeEmails = granteeNames + RemovedGranteeEmails = granteeEmails } }; @@ -98,10 +98,10 @@ public class EmergencyAccessMailTests // Assert - All grantee names should appear in the email Assert.NotNull(sentMessage); - foreach (var granteeName in granteeNames) + foreach (var granteeEmail in granteeEmails) { - Assert.Contains(granteeName, sentMessage.TextContent); - Assert.Contains(granteeName, sentMessage.HtmlContent); + Assert.Contains(granteeEmail, sentMessage.TextContent); + Assert.Contains(granteeEmail, sentMessage.HtmlContent); } } @@ -148,6 +148,6 @@ public class EmergencyAccessMailTests Assert.NotNull(mail); Assert.NotNull(mail.View); Assert.Equal(_emergencyAccessMailSubject, mail.Subject); - Assert.Equal(_emergencyAccessHelpUrl, EmergencyAccessRemoveGranteesMailView.EmergencyAccessHelpPageUrl); + Assert.Equal(_emergencyAccessHelpUrl, mail.View.EmergencyAccessHelpPageUrl); } }