diff --git a/SeaHaven.Services/Implementation/TeamMemberInviteService.cs b/SeaHaven.Services/Implementation/TeamMemberInviteService.cs index 3b355ba..e78130d 100644 --- a/SeaHaven.Services/Implementation/TeamMemberInviteService.cs +++ b/SeaHaven.Services/Implementation/TeamMemberInviteService.cs @@ -89,13 +89,13 @@ public sealed class TeamMemberInviteService : ITeamMemberInviteService if (user.IsDeleted == true || user.PendingRegistration != true || string.IsNullOrWhiteSpace(user.Email)) return ResendFailure("Only pending team members can be re-invited."); - if (!await ReissueAsync(user, cancellationToken)) + if (await ReissueAsync(user, cancellationToken) != true) return ResendFailure("The invite could not be emailed. Try again."); return new TeamMemberInviteResendOutcomeDTO { Success = true }; } - public async Task ReissueAsync(ApplicationUser user, CancellationToken cancellationToken) + public async Task ReissueAsync(ApplicationUser user, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(user); ArgumentException.ThrowIfNullOrWhiteSpace(user.Email); @@ -105,7 +105,7 @@ public sealed class TeamMemberInviteService : ITeamMemberInviteService // A deactivated member cannot register; the admin re-sends the invite after reactivating them. if (user.IsDeleted == true) - return true; + return null; var name = $"{user.FirstName ?? ""} {user.LastName ?? ""}".Trim(); return await SendAsync(issued, user.Email, name, cancellationToken); diff --git a/SeaHaven.Services/Interfaces/ITeamMemberInviteService.cs b/SeaHaven.Services/Interfaces/ITeamMemberInviteService.cs index 54eb702..25d5b6b 100644 --- a/SeaHaven.Services/Interfaces/ITeamMemberInviteService.cs +++ b/SeaHaven.Services/Interfaces/ITeamMemberInviteService.cs @@ -19,9 +19,9 @@ public interface ITeamMemberInviteService /// /// Revokes the member's open invites, including any email confirmation made through them, /// and emails a new invite to their current address unless they are deactivated. - /// Returns false when that email failed. + /// Returns whether that email went out, or null when the member is deactivated and nothing was emailed. /// - Task ReissueAsync(ApplicationUser user, CancellationToken cancellationToken); + Task ReissueAsync(ApplicationUser user, CancellationToken cancellationToken); /// Admin-only: revokes a pending member's open invites and emails a new one. Task ResendAsync( diff --git a/SeaHavenIndustries.Tests/TeamMemberInviteLifecycleTests.cs b/SeaHavenIndustries.Tests/TeamMemberInviteLifecycleTests.cs index c963a62..9ddb383 100644 --- a/SeaHavenIndustries.Tests/TeamMemberInviteLifecycleTests.cs +++ b/SeaHavenIndustries.Tests/TeamMemberInviteLifecycleTests.cs @@ -74,6 +74,21 @@ public sealed class TeamMemberInviteLifecycleTests Assert.False(updated.Member.InviteEmailSent); } + [Fact] + public async Task DeactivatingAPendingMemberWhileChangingTheirEmail_RevokesTheInviteWithoutClaimingAnEmail() + { + await using var host = await TeamMemberInviteTestHost.CreateAsync(); + var (userId, oldToken) = await host.AddPendingMemberAsync(Email); + + var updated = await UpdateAsync(host, userId, CorrectedEmail, role: "dispatcher", isActive: false); + + Assert.True(updated.Success, updated.Error); + Assert.Null(updated.Member!.InviteEmailSent); + Assert.DoesNotContain(host.Sent.Messages, message => message.To == CorrectedEmail); + Assert.Equal(TeamMemberRegistrationStatus.InvalidInvite, + (await host.RegistrationAsync(service => service.ResolveAsync(oldToken, CancellationToken.None))).Status); + } + [Fact] public async Task EditingAPendingMemberWithoutChangingTheirEmail_KeepsTheirInvite() { @@ -96,7 +111,8 @@ public sealed class TeamMemberInviteLifecycleTests TeamMemberInviteTestHost host, string userId, string email, - string role) => + string role, + bool isActive = true) => host.InScopeAsync(provider => provider.GetRequiredService().UpdateAsync( userId, new UpdateTeamMemberRequestDTO @@ -106,7 +122,8 @@ public sealed class TeamMemberInviteLifecycleTests Color = "#0D9488", Email = email, Phone = "555-0100", - ServiceAreas = role == "dispatcher" ? new[] { "East" } : Array.Empty() + ServiceAreas = role == "dispatcher" ? new[] { "East" } : Array.Empty(), + IsActive = isActive }, TeamMemberInviteTestHost.Admin(), CancellationToken.None));