diff --git a/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByOrganizationId.sql index 526a9141ac..3a93687d25 100644 --- a/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByOrganizationId.sql +++ b/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByOrganizationId.sql @@ -52,6 +52,7 @@ BEGIN -- Return policy details for each matching organization user. SELECT OU.[OrganizationUserId], + OU.[UserId], P.[OrganizationId], P.[Type] AS [PolicyType], P.[Data] AS [PolicyData], diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByOrganizationIdAsyncTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByOrganizationIdAsyncTests.cs index 7dc4b6d2b3..e1352f5c8b 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByOrganizationIdAsyncTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByOrganizationIdAsyncTests.cs @@ -40,6 +40,10 @@ public class GetPolicyDetailsByOrganizationIdAsyncTests Assert.True(results.Single().IsProvider); + // Annul + await organizationRepository.DeleteAsync(new Organization { Id = userOrgConnectedDirectly.OrganizationId }); + await userRepository.DeleteAsync(user); + async Task ArrangeProvider() { var provider = await providerRepository.CreateAsync(new Provider @@ -86,6 +90,11 @@ public class GetPolicyDetailsByOrganizationIdAsyncTests Assert.Contains(results, result => result.OrganizationUserId == userOrgConnectedDirectly.Id && result.OrganizationId == userOrgConnectedDirectly.OrganizationId); Assert.DoesNotContain(results, result => result.OrganizationId == notConnectedOrg.Id); + + // Annul + await organizationRepository.DeleteAsync(new Organization { Id = userOrgConnectedDirectly.OrganizationId }); + await organizationRepository.DeleteAsync(notConnectedOrg); + await userRepository.DeleteAsync(user); } [DatabaseTheory, DatabaseData] @@ -115,6 +124,10 @@ public class GetPolicyDetailsByOrganizationIdAsyncTests && result.PolicyType == inputPolicyType); Assert.DoesNotContain(results, result => result.PolicyType == notInputPolicyType); + + // Annul + await organizationRepository.DeleteAsync(new Organization { Id = orgUser.OrganizationId }); + await userRepository.DeleteAsync(user); } @@ -143,6 +156,12 @@ public class GetPolicyDetailsByOrganizationIdAsyncTests Assert.Equal(expectedCount, results.Count); AssertPolicyDetailUserConnections(results, userOrgConnectedDirectly, userOrgConnectedByEmail, userOrgConnectedByUserId); + + // Annul + await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedDirectly.OrganizationId }); + await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByEmail.OrganizationId }); + await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByUserId.OrganizationId }); + await userRepository.DeleteAsync(user); } [DatabaseTheory, DatabaseData] @@ -167,8 +186,52 @@ public class GetPolicyDetailsByOrganizationIdAsyncTests // Assert AssertPolicyDetailUserConnections(results, userOrgConnectedDirectly, userOrgConnectedByEmail, userOrgConnectedByUserId); + + // Annul + await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedDirectly.OrganizationId }); + await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByEmail.OrganizationId }); + await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByUserId.OrganizationId }); + await userRepository.DeleteAsync(user); } + [DatabaseTheory, DatabaseData] + public async Task ShouldReturnUserIds( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user1 = await userRepository.CreateTestUserAsync(); + var user2 = await userRepository.CreateTestUserAsync(); + const PolicyType policyType = PolicyType.SingleOrg; + + var organization = await CreateEnterpriseOrg(organizationRepository); + await policyRepository.CreateAsync(new Policy { OrganizationId = organization.Id, Enabled = true, Type = policyType }); + + var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user1); + var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user2); + + // Act + var results = (await policyRepository.GetPolicyDetailsByOrganizationIdAsync(organization.Id, policyType)).ToList(); + + // Assert + Assert.Equal(2, results.Count); + + Assert.Contains(results, result => result.OrganizationUserId == orgUser1.Id + && result.UserId == orgUser1.UserId + && result.OrganizationId == orgUser1.OrganizationId); + + Assert.Contains(results, result => result.OrganizationUserId == orgUser2.Id + && result.UserId == orgUser2.UserId + && result.OrganizationId == orgUser2.OrganizationId); + + // Annul + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteManyAsync([user1, user2]); + } + + private async Task ArrangeOtherOrgConnectedByUserIdAsync(IOrganizationUserRepository organizationUserRepository, IOrganizationRepository organizationRepository, IPolicyRepository policyRepository, User user, PolicyType policyType) diff --git a/util/Migrator/DbScripts/2025-08-15_00_PolicyDetails_ReadByOrganizationId_AddUserId.sql b/util/Migrator/DbScripts/2025-08-15_00_PolicyDetails_ReadByOrganizationId_AddUserId.sql new file mode 100644 index 0000000000..0e4dde6e02 --- /dev/null +++ b/util/Migrator/DbScripts/2025-08-15_00_PolicyDetails_ReadByOrganizationId_AddUserId.sql @@ -0,0 +1,82 @@ +CREATE OR ALTER PROCEDURE [dbo].[PolicyDetails_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER, + @PolicyType TINYINT +AS +BEGIN + SET NOCOUNT ON; + + -- Get users in the given organization (@OrganizationId) by matching either on UserId or Email. + ;WITH GivenOrgUsers AS ( + SELECT + OU.[UserId], + U.[Email] + FROM [dbo].[OrganizationUserView] OU + INNER JOIN [dbo].[UserView] U ON U.[Id] = OU.[UserId] + WHERE OU.[OrganizationId] = @OrganizationId + + UNION ALL + + SELECT + U.[Id] AS [UserId], + U.[Email] + FROM [dbo].[OrganizationUserView] OU + INNER JOIN [dbo].[UserView] U ON U.[Email] = OU.[Email] + WHERE OU.[OrganizationId] = @OrganizationId + ), + + -- Retrieve all organization users that match on either UserId or Email from GivenOrgUsers. + AllOrgUsers AS ( + SELECT + OU.[Id] AS [OrganizationUserId], + OU.[UserId], + OU.[OrganizationId], + AU.[Email], + OU.[Type] AS [OrganizationUserType], + OU.[Status] AS [OrganizationUserStatus], + OU.[Permissions] AS [OrganizationUserPermissionsData] + FROM [dbo].[OrganizationUserView] OU + INNER JOIN GivenOrgUsers AU ON AU.[UserId] = OU.[UserId] + UNION ALL + SELECT + OU.[Id] AS [OrganizationUserId], + AU.[UserId], + OU.[OrganizationId], + AU.[Email], + OU.[Type] AS [OrganizationUserType], + OU.[Status] AS [OrganizationUserStatus], + OU.[Permissions] AS [OrganizationUserPermissionsData] + FROM [dbo].[OrganizationUserView] OU + INNER JOIN GivenOrgUsers AU ON AU.[Email] = OU.[Email] + ) + + -- Return policy details for each matching organization user. + SELECT + OU.[OrganizationUserId], + OU.[UserId], + P.[OrganizationId], + P.[Type] AS [PolicyType], + P.[Data] AS [PolicyData], + OU.[OrganizationUserType], + OU.[OrganizationUserStatus], + OU.[OrganizationUserPermissionsData], + -- Check if user is a provider for the organization + CASE + WHEN EXISTS ( + SELECT 1 + FROM [dbo].[ProviderUserView] PU + INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId] + WHERE PU.[UserId] = OU.[UserId] + AND PO.[OrganizationId] = P.[OrganizationId] + ) THEN 1 + ELSE 0 + END AS [IsProvider] + FROM [dbo].[PolicyView] P + INNER JOIN [dbo].[OrganizationView] O ON P.[OrganizationId] = O.[Id] + INNER JOIN AllOrgUsers OU ON OU.[OrganizationId] = O.[Id] + WHERE P.[Enabled] = 1 + AND O.[Enabled] = 1 + AND O.[UsePolicies] = 1 + AND P.[Type] = @PolicyType + +END +GO \ No newline at end of file