mirror of
https://github.com/bitwarden/server
synced 2026-02-19 19:03:30 +00:00
[PM-26376] Emergency Access Delete Command (#6857)
* feat: Add initial DeleteEmergencyContactCommand * chore: remove nullable enable and add comments * test: add tests for new delete command * test: update tests to test IMailer was called. * feat: add delete by GranteeId and allow for multiple grantors to be contacted. * feat: add DeleteMany stored procedure for EmergencyAccess * test: add database tests for new SP * feat: commands use DeleteManyById for emergencyAccessDeletes * claude: send one email per grantor instead of a bulk email to all grantors. Modified tests to validate. * feat: change revision dates for confirmed grantees; * feat: add AccountRevisionDate bump for grantee users in the confirmed status * test: update integration test to validate only confirmed users are updated as well as proper deletion of emergency access
This commit is contained in:
@@ -30,7 +30,7 @@ public class EmergencyAccessRotationValidatorTests
|
||||
KeyEncrypted = e.KeyEncrypted,
|
||||
Type = e.Type
|
||||
}).ToList();
|
||||
userEmergencyAccess.Add(new EmergencyAccessDetails { Id = Guid.NewGuid(), KeyEncrypted = "TestKey" });
|
||||
userEmergencyAccess.Add(new EmergencyAccessDetails { Id = Guid.NewGuid(), GrantorEmail = "grantor@example.com", KeyEncrypted = "TestKey" });
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>().GetManyDetailsByGrantorIdAsync(user.Id)
|
||||
.Returns(userEmergencyAccess);
|
||||
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.UserFeatures.EmergencyAccess.Commands;
|
||||
using Bit.Core.Auth.UserFeatures.EmergencyAccess.Mail;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Platform.Mail.Mailer;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.UserFeatures.EmergencyAccess;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteEmergencyAccessCommandTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies that attempting to delete a non-existent emergency access record
|
||||
/// throws a <see cref="BadRequestException"/> and does not call delete or send email.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteByIdGrantorIdAsync_EmergencyAccessNotFound_ThrowsBadRequest(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
Guid emergencyAccessId,
|
||||
Guid grantorId)
|
||||
{
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetDetailsByIdGrantorIdAsync(emergencyAccessId, grantorId)
|
||||
.Returns((EmergencyAccessDetails)null);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.DeleteByIdGrantorIdAsync(emergencyAccessId, grantorId));
|
||||
|
||||
Assert.Contains("Emergency Access not valid.", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendEmail<EmergencyAccessRemoveGranteesMailView>(default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies successful deletion of an emergency access record by ID and grantor ID,
|
||||
/// and ensures that a notification email is sent to the grantor.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteByIdGrantorIdAsync_DeletesEmergencyAccessAndSendsEmail(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
EmergencyAccessDetails emergencyAccessDetails)
|
||||
{
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetDetailsByIdGrantorIdAsync(emergencyAccessDetails.Id, emergencyAccessDetails.GrantorId)
|
||||
.Returns(emergencyAccessDetails);
|
||||
|
||||
var result = await sutProvider.Sut.DeleteByIdGrantorIdAsync(emergencyAccessDetails.Id, emergencyAccessDetails.GrantorId);
|
||||
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Any<ICollection<Guid>>());
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.Received(1)
|
||||
.SendEmail(Arg.Any<EmergencyAccessRemoveGranteesMail>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAllByGrantorIdAsync_NoEmergencyAccessRecords_ReturnsEmptyCollection(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
Guid grantorId)
|
||||
{
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetManyDetailsByGrantorIdAsync(grantorId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.DeleteAllByGrantorIdAsync(grantorId);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendEmail<EmergencyAccessRemoveGranteesMailView>(default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 to the grantor.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAllByGrantorIdAsync_MultipleRecords_DeletesAllReturnsDetailsSendsSingleEmail(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
EmergencyAccessDetails emergencyAccessDetails1,
|
||||
EmergencyAccessDetails emergencyAccessDetails2,
|
||||
EmergencyAccessDetails emergencyAccessDetails3)
|
||||
{
|
||||
// Arrange
|
||||
// link all details to the same grantor
|
||||
emergencyAccessDetails2.GrantorId = emergencyAccessDetails1.GrantorId;
|
||||
emergencyAccessDetails2.GrantorEmail = emergencyAccessDetails1.GrantorEmail;
|
||||
emergencyAccessDetails3.GrantorId = emergencyAccessDetails1.GrantorId;
|
||||
emergencyAccessDetails3.GrantorEmail = emergencyAccessDetails1.GrantorEmail;
|
||||
|
||||
var allDetails = new List<EmergencyAccessDetails>
|
||||
{
|
||||
emergencyAccessDetails1,
|
||||
emergencyAccessDetails2,
|
||||
emergencyAccessDetails3
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetManyDetailsByGrantorIdAsync(emergencyAccessDetails1.GrantorId)
|
||||
.Returns(allDetails);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.DeleteAllByGrantorIdAsync(emergencyAccessDetails1.GrantorId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(3, result.Count);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Any<ICollection<Guid>>());
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.Received(1)
|
||||
.SendEmail(Arg.Any<EmergencyAccessRemoveGranteesMail>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when a grantor has a single emergency access record, it is deleted,
|
||||
/// the details are returned, and a notification email is sent.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAllByGrantorIdAsync_SingleRecord_DeletesAndReturnsDetailsSendsSingleEmail(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
EmergencyAccessDetails emergencyAccessDetails,
|
||||
Guid grantorId)
|
||||
{
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetManyDetailsByGrantorIdAsync(grantorId)
|
||||
.Returns([emergencyAccessDetails]);
|
||||
|
||||
var result = await sutProvider.Sut.DeleteAllByGrantorIdAsync(grantorId);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
Assert.Equal(emergencyAccessDetails.Id, result.First().Id);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Any<ICollection<Guid>>());
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.Received(1)
|
||||
.SendEmail(Arg.Any<EmergencyAccessRemoveGranteesMail>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAllByGranteeIdAsync_NoEmergencyAccessRecords_ReturnsEmptyCollection(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
Guid granteeId)
|
||||
{
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetManyDetailsByGranteeIdAsync(granteeId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.DeleteAllByGranteeIdAsync(granteeId);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendEmail<EmergencyAccessRemoveGranteesMailView>(default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAllByGranteeIdAsync_SingleRecord_DeletesAndReturnsDetailsSendsSingleEmail(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
EmergencyAccessDetails emergencyAccessDetails,
|
||||
Guid granteeId)
|
||||
{
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.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<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Any<ICollection<Guid>>());
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.Received(1)
|
||||
.SendEmail(Arg.Any<EmergencyAccessRemoveGranteesMail>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 individually.
|
||||
/// </summary>
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAllByGranteeIdAsync_MultipleRecords_DeletesAllReturnsDetailsSendsMultipleEmails(
|
||||
SutProvider<DeleteEmergencyAccessCommand> sutProvider,
|
||||
EmergencyAccessDetails emergencyAccessDetails1,
|
||||
EmergencyAccessDetails emergencyAccessDetails2,
|
||||
EmergencyAccessDetails emergencyAccessDetails3)
|
||||
{
|
||||
// link all details to the same grantee
|
||||
emergencyAccessDetails2.GranteeId = emergencyAccessDetails1.GranteeId;
|
||||
emergencyAccessDetails2.GranteeEmail = emergencyAccessDetails1.GranteeEmail;
|
||||
emergencyAccessDetails3.GranteeId = emergencyAccessDetails1.GranteeId;
|
||||
emergencyAccessDetails3.GranteeEmail = emergencyAccessDetails1.GranteeEmail;
|
||||
|
||||
var allDetails = new List<EmergencyAccessDetails>
|
||||
{
|
||||
emergencyAccessDetails1,
|
||||
emergencyAccessDetails2,
|
||||
emergencyAccessDetails3
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetManyDetailsByGranteeIdAsync((Guid)emergencyAccessDetails1.GranteeId)
|
||||
.Returns(allDetails);
|
||||
|
||||
var result = await sutProvider.Sut.DeleteAllByGranteeIdAsync((Guid)emergencyAccessDetails1.GranteeId);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(3, result.Count);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Any<ICollection<Guid>>());
|
||||
await sutProvider.GetDependency<IMailer>()
|
||||
.Received(allDetails.Count)
|
||||
.SendEmail(Arg.Any<EmergencyAccessRemoveGranteesMail>());
|
||||
}
|
||||
}
|
||||
@@ -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<ILogger<HandlebarMailRenderer>>();
|
||||
@@ -41,7 +41,7 @@ public class EmergencyAccessMailTests
|
||||
ToEmails = [grantorEmail],
|
||||
View = new EmergencyAccessRemoveGranteesMailView
|
||||
{
|
||||
RemovedGranteeNames = [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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
{
|
||||
RemovedGranteeNames = 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,14 +119,14 @@ public class EmergencyAccessMailTests
|
||||
View = new EmergencyAccessRemoveGranteesMailView
|
||||
{
|
||||
// Required: at least one removed grantee name
|
||||
RemovedGranteeNames = ["Example Grantee"]
|
||||
RemovedGranteeEmails = ["Example Grantee"]
|
||||
}
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(mail);
|
||||
Assert.NotNull(mail.View);
|
||||
Assert.NotEmpty(mail.View.RemovedGranteeNames);
|
||||
Assert.NotEmpty(mail.View.RemovedGranteeEmails);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,13 +141,13 @@ public class EmergencyAccessMailTests
|
||||
var mail = new EmergencyAccessRemoveGranteesMail
|
||||
{
|
||||
ToEmails = [grantorEmail],
|
||||
View = new EmergencyAccessRemoveGranteesMailView { RemovedGranteeNames = [granteeName] }
|
||||
View = new EmergencyAccessRemoveGranteesMailView { RemovedGranteeEmails = [granteeName] }
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(mail);
|
||||
Assert.NotNull(mail.View);
|
||||
Assert.Equal(_emergencyAccessMailSubject, mail.Subject);
|
||||
Assert.Equal(_emergencyAccessHelpUrl, mail.View.EmergencyAccessHelpPageUrl);
|
||||
Assert.Equal(_emergencyAccessHelpUrl, EmergencyAccessRemoveGranteesMailView.EmergencyAccessHelpPageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,4 +42,97 @@ public class EmergencyAccessRepositoriesTests
|
||||
Assert.NotNull(updatedGrantee);
|
||||
Assert.NotEqual(updatedGrantee.AccountRevisionDate, granteeUser.AccountRevisionDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates 3 Emergency Access records all connected to a single grantor, but separate grantees.
|
||||
/// All 3 records are then deleted in a single call to DeleteManyAsync.
|
||||
/// </summary>
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task DeleteManyAsync_DeletesMultipleGranteeRecords_UpdatesUserRevisionDates(
|
||||
IUserRepository userRepository,
|
||||
IEmergencyAccessRepository emergencyAccessRepository)
|
||||
{
|
||||
// Arrange
|
||||
var grantorUser = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test Grantor User",
|
||||
Email = $"test+grantor{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var confirmedGranteeUser1 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test Grantee User 1",
|
||||
Email = $"test+grantee{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var invitedGranteeUser2 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test Grantee User 2",
|
||||
Email = $"test+grantee{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
// The inmemory datetime has a precision issue, so we need to refresh the user to get the stored AccountRevisionDate
|
||||
invitedGranteeUser2 = await userRepository.GetByIdAsync(invitedGranteeUser2.Id);
|
||||
|
||||
var granteeUser3 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test Grantee User 3",
|
||||
Email = $"test+grantee{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var confirmedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
|
||||
{
|
||||
GrantorId = grantorUser.Id,
|
||||
GranteeId = confirmedGranteeUser1.Id,
|
||||
Status = EmergencyAccessStatusType.Confirmed,
|
||||
});
|
||||
|
||||
var invitedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
|
||||
{
|
||||
GrantorId = grantorUser.Id,
|
||||
GranteeId = invitedGranteeUser2.Id,
|
||||
Status = EmergencyAccessStatusType.Invited,
|
||||
});
|
||||
|
||||
var acceptedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
|
||||
{
|
||||
GrantorId = grantorUser.Id,
|
||||
GranteeId = granteeUser3.Id,
|
||||
Status = EmergencyAccessStatusType.Accepted,
|
||||
});
|
||||
|
||||
|
||||
// Act
|
||||
await emergencyAccessRepository.DeleteManyAsync([confirmedEmergencyAccess.Id, invitedEmergencyAccess.Id, acceptedEmergencyAccess.Id]);
|
||||
|
||||
// Assert
|
||||
// ensure Grantor records deleted
|
||||
var grantorEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(grantorUser.Id);
|
||||
Assert.Empty(grantorEmergencyAccess);
|
||||
|
||||
// ensure Grantee records deleted
|
||||
foreach (var grantee in (List<User>)[confirmedGranteeUser1, invitedGranteeUser2, granteeUser3])
|
||||
{
|
||||
var granteeEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(grantee.Id);
|
||||
Assert.Empty(granteeEmergencyAccess);
|
||||
}
|
||||
|
||||
// Only the Status.Confirmed grantee's AccountRevisionDate should be updated
|
||||
var updatedConfirmedGrantee = await userRepository.GetByIdAsync(confirmedGranteeUser1.Id);
|
||||
Assert.NotNull(updatedConfirmedGrantee);
|
||||
Assert.NotEqual(updatedConfirmedGrantee.AccountRevisionDate, confirmedGranteeUser1.AccountRevisionDate);
|
||||
|
||||
// Invited user should not have an updated AccountRevisionDate
|
||||
var updatedInvitedGrantee = await userRepository.GetByIdAsync(invitedGranteeUser2.Id);
|
||||
Assert.NotNull(updatedInvitedGrantee);
|
||||
Assert.Equal(updatedInvitedGrantee.AccountRevisionDate, invitedGranteeUser2.AccountRevisionDate);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user