using Elsa.Testing.Shared.Multitenancy; using Elsa.Common; using Elsa.Common.Models; using Elsa.Common.Services; using Elsa.ExternalAuthentication.Contracts; using Elsa.ExternalAuthentication.Models; using Elsa.ExternalAuthentication.Services; using Elsa.Identity.Contracts; using Elsa.Identity.Entities; using Elsa.Identity.Models; using Elsa.Identity.Services; using NSubstitute; namespace Elsa.ExternalAuthentication.UnitTests.Foundational; public class ExternalAuthenticationUserDeletionDependencyContributorTests { [Fact] public async Task UserWithAnExternalIdentityLinkCannotBeDeleted() { var users = new MemoryUserStore(new MemoryStore(), new TestTenantAccessor("tenant-a")); await users.SaveAsync(new User { Id = "external-user", Name = "external-user", TenantId = "tenant-a" }); var links = Substitute.For(); links.FindAsync( Arg.Is(x => x.TenantId == "tenant-a" && x.UserId == "external-user"), Arg.Any()) .Returns(ValueTask.FromResult(Page.Of([ new ExternalIdentityLink("link-a", "tenant-a", "contoso", "https://issuer.example", "subject-hash", null, "external-user", DateTimeOffset.UtcNow, null) ], 1))); var coordinator = new UserDeletionCoordinator( users, [new ExternalAuthenticationUserDeletionDependencyContributor(links)]); var result = await coordinator.DeleteAsync("external-user"); var blocked = Assert.IsType(result); Assert.Contains(blocked.Dependencies, x => x.Source == ExternalAuthenticationUserDeletionDependencyContributor.SourceName); Assert.NotNull(await users.FindAsync(new UserFilter { Id = "external-user" })); } [Fact] public async Task UserWithoutAnExternalIdentityLinkCanBeDeleted() { var users = new MemoryUserStore(new MemoryStore(), new TestTenantAccessor("tenant-a")); await users.SaveAsync(new User { Id = "local-user", Name = "local-user", TenantId = "tenant-a" }); var links = Substitute.For(); links.FindAsync( Arg.Is(x => x.TenantId == "tenant-a" && x.UserId == "local-user"), Arg.Any()) .Returns(ValueTask.FromResult(Page.Of([], 0))); var coordinator = new UserDeletionCoordinator( users, [new ExternalAuthenticationUserDeletionDependencyContributor(links)]); var result = await coordinator.DeleteAsync("local-user"); Assert.IsType(result); Assert.Null(await users.FindAsync(new UserFilter { Id = "local-user" })); } [Fact] public async Task UserIsRestoredWhenAnExternalIdentityLinkAppearsDuringDeletion() { var users = new MemoryUserStore(new MemoryStore(), new TestTenantAccessor("tenant-a")); await users.SaveAsync(new User { Id = "racing-user", Name = "racing-user", TenantId = "tenant-a" }); var links = Substitute.For(); var inspectionCount = 0; links.FindAsync(Arg.Any(), Arg.Any()) .Returns(_ => ValueTask.FromResult(Interlocked.Increment(ref inspectionCount) == 1 ? Page.Of([], 0) : Page.Of([ new ExternalIdentityLink("link-a", "tenant-a", "contoso", "https://issuer.example", "subject-hash", null, "racing-user", DateTimeOffset.UtcNow, null) ], 1))); var coordinator = new UserDeletionCoordinator( users, [new ExternalAuthenticationUserDeletionDependencyContributor(links)]); var result = await coordinator.DeleteAsync("racing-user"); Assert.IsType(result); Assert.NotNull(await users.FindAsync(new UserFilter { Id = "racing-user" })); } }