From 651d5e3aacb6ccba820d6655ef60519e9c4a5655 Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Mon, 8 Dec 2025 16:30:22 +0000 Subject: [PATCH] Refactor HasPremiumAccessQuery and IHasPremiumAccessQuery to streamline premium access checks. Updated method names for clarity and improved documentation. Adjusted test cases to reflect changes in user premium access retrieval logic. --- .../Premium/Queries/HasPremiumAccessQuery.cs | 17 +-- .../Premium/Queries/IHasPremiumAccessQuery.cs | 36 ++--- .../Queries/HasPremiumAccessQueryTests.cs | 125 ++++++++++-------- 3 files changed, 91 insertions(+), 87 deletions(-) diff --git a/src/Core/Billing/Premium/Queries/HasPremiumAccessQuery.cs b/src/Core/Billing/Premium/Queries/HasPremiumAccessQuery.cs index 6e375e784a..910ce62a52 100644 --- a/src/Core/Billing/Premium/Queries/HasPremiumAccessQuery.cs +++ b/src/Core/Billing/Premium/Queries/HasPremiumAccessQuery.cs @@ -3,10 +3,6 @@ using Bit.Core.Repositories; namespace Bit.Core.Billing.Premium.Queries; -/// -/// Query for checking premium access status for users using the existing stored procedure -/// that calculates premium access from personal subscriptions and organization memberships. -/// public class HasPremiumAccessQuery : IHasPremiumAccessQuery { private readonly IUserRepository _userRepository; @@ -18,17 +14,18 @@ public class HasPremiumAccessQuery : IHasPremiumAccessQuery public async Task HasPremiumAccessAsync(Guid userId) { - var user = await _userRepository.GetCalculatedPremiumAsync(userId); + var user = await _userRepository.GetPremiumAccessAsync(userId); if (user == null) { throw new NotFoundException(); } + return user.HasPremiumAccess; } public async Task> HasPremiumAccessAsync(IEnumerable userIds) { - var usersWithPremium = await _userRepository.GetManyWithCalculatedPremiumAsync(userIds); + var usersWithPremium = await _userRepository.GetPremiumAccessByIdsAsync(userIds); if (usersWithPremium.Count() != userIds.Count()) { @@ -40,16 +37,12 @@ public class HasPremiumAccessQuery : IHasPremiumAccessQuery public async Task HasPremiumFromOrganizationAsync(Guid userId) { - var user = await _userRepository.GetCalculatedPremiumAsync(userId); + var user = await _userRepository.GetPremiumAccessAsync(userId); if (user == null) { throw new NotFoundException(); } - // Has org premium if has premium access but not personal premium - return user.HasPremiumAccess && !user.Premium; + return user.OrganizationPremium; } } - - - diff --git a/src/Core/Billing/Premium/Queries/IHasPremiumAccessQuery.cs b/src/Core/Billing/Premium/Queries/IHasPremiumAccessQuery.cs index e746c0c158..e5545b1ade 100644 --- a/src/Core/Billing/Premium/Queries/IHasPremiumAccessQuery.cs +++ b/src/Core/Billing/Premium/Queries/IHasPremiumAccessQuery.cs @@ -1,38 +1,30 @@ namespace Bit.Core.Billing.Premium.Queries; /// -/// Query for checking premium access status for users. -/// This is the centralized location for determining if a user can access premium features -/// (either through personal subscription or organization membership). -/// -/// -/// Note: This is different from checking User.Premium, which only indicates -/// personal subscription status. Use these methods to check actual premium feature access. -/// +/// Centralized query for checking if users have premium access through personal subscriptions or organizations. +/// Note: Different from User.Premium which only checks personal subscriptions. /// public interface IHasPremiumAccessQuery { /// - /// Checks if a user has access to premium features (personal subscription or organization). - /// This is the definitive way to check premium access for a single user. + /// Checks if a user has premium access (personal or organization). /// - /// The user ID to check for premium access - /// True if user can access premium features; false otherwise + /// The user ID to check + /// True if user can access premium features Task HasPremiumAccessAsync(Guid userId); /// - /// Checks if a user has access to premium features through organization membership only. - /// This is useful for determining the source of premium access (personal vs organization). + /// Checks premium access for multiple users. /// - /// The user ID to check for organization premium access - /// True if user has premium access through any organization; false otherwise - Task HasPremiumFromOrganizationAsync(Guid userId); + /// The user IDs to check + /// Dictionary mapping user IDs to their premium access status + Task> HasPremiumAccessAsync(IEnumerable userIds); /// - /// Checks if multiple users have access to premium features (optimized bulk operation). - /// Uses existing stored procedure that calculates premium from personal subscriptions and organizations. + /// Checks if a user belongs to any organization that grants premium (enabled org with UsersGetPremium). + /// Returns true regardless of personal subscription. Useful for UI decisions like showing subscription options. /// - /// The user IDs to check for premium access - /// Dictionary mapping user IDs to their premium access status (personal or through organization) - Task> HasPremiumAccessAsync(IEnumerable userIds); + /// The user ID to check + /// True if user is in any organization that grants premium + Task HasPremiumFromOrganizationAsync(Guid userId); } diff --git a/test/Core.Test/Billing/Premium/Queries/HasPremiumAccessQueryTests.cs b/test/Core.Test/Billing/Premium/Queries/HasPremiumAccessQueryTests.cs index c77a354589..b52bc68abc 100644 --- a/test/Core.Test/Billing/Premium/Queries/HasPremiumAccessQueryTests.cs +++ b/test/Core.Test/Billing/Premium/Queries/HasPremiumAccessQueryTests.cs @@ -1,5 +1,5 @@ -using Bit.Core.Billing.Premium.Queries; -using Bit.Core.Models.Data; +using Bit.Core.Billing.Premium.Models; +using Bit.Core.Billing.Premium.Queries; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -13,15 +13,15 @@ public class HasPremiumAccessQueryTests { [Theory, BitAutoData] public async Task HasPremiumAccessAsync_WhenUserHasPersonalPremium_ReturnsTrue( - UserWithCalculatedPremium user, + UserPremiumAccess user, SutProvider sutProvider) { // Arrange - user.Premium = true; - user.HasPremiumAccess = true; + user.PersonalPremium = true; + user.OrganizationPremium = false; sutProvider.GetDependency() - .GetCalculatedPremiumAsync(user.Id) + .GetPremiumAccessAsync(user.Id) .Returns(user); // Act @@ -33,15 +33,15 @@ public class HasPremiumAccessQueryTests [Theory, BitAutoData] public async Task HasPremiumAccessAsync_WhenUserHasNoPersonalPremiumButHasOrgPremium_ReturnsTrue( - UserWithCalculatedPremium user, + UserPremiumAccess user, SutProvider sutProvider) { // Arrange - user.Premium = false; - user.HasPremiumAccess = true; // Has org premium + user.PersonalPremium = false; + user.OrganizationPremium = true; // Has org premium sutProvider.GetDependency() - .GetCalculatedPremiumAsync(user.Id) + .GetPremiumAccessAsync(user.Id) .Returns(user); // Act @@ -53,15 +53,15 @@ public class HasPremiumAccessQueryTests [Theory, BitAutoData] public async Task HasPremiumAccessAsync_WhenUserHasNoPersonalPremiumAndNoOrgPremium_ReturnsFalse( - UserWithCalculatedPremium user, + UserPremiumAccess user, SutProvider sutProvider) { // Arrange - user.Premium = false; - user.HasPremiumAccess = false; + user.PersonalPremium = false; + user.OrganizationPremium = false; sutProvider.GetDependency() - .GetCalculatedPremiumAsync(user.Id) + .GetPremiumAccessAsync(user.Id) .Returns(user); // Act @@ -72,33 +72,31 @@ public class HasPremiumAccessQueryTests } [Theory, BitAutoData] - public async Task HasPremiumAccessAsync_WhenUserNotFound_ReturnsFalse( + public async Task HasPremiumAccessAsync_WhenUserNotFound_ThrowsNotFoundException( Guid userId, SutProvider sutProvider) { // Arrange sutProvider.GetDependency() - .GetCalculatedPremiumAsync(userId) - .Returns((UserWithCalculatedPremium)null); + .GetPremiumAccessAsync(userId) + .Returns(_ => throw new Bit.Core.Exceptions.NotFoundException()); - // Act - var result = await sutProvider.Sut.HasPremiumAccessAsync(userId); - - // Assert - Assert.False(result); + // Act & Assert + await Assert.ThrowsAsync( + () => sutProvider.Sut.HasPremiumAccessAsync(userId)); } [Theory, BitAutoData] public async Task HasPremiumFromOrganizationAsync_WhenUserHasNoOrganizations_ReturnsFalse( - UserWithCalculatedPremium user, + UserPremiumAccess user, SutProvider sutProvider) { // Arrange - user.Premium = false; - user.HasPremiumAccess = false; // No premium from anywhere + user.PersonalPremium = false; + user.OrganizationPremium = false; // No premium from anywhere sutProvider.GetDependency() - .GetCalculatedPremiumAsync(user.Id) + .GetPremiumAccessAsync(user.Id) .Returns(user); // Act @@ -110,15 +108,15 @@ public class HasPremiumAccessQueryTests [Theory, BitAutoData] public async Task HasPremiumFromOrganizationAsync_WhenUserHasPremiumFromOrg_ReturnsTrue( - UserWithCalculatedPremium user, + UserPremiumAccess user, SutProvider sutProvider) { // Arrange - user.Premium = false; // No personal premium - user.HasPremiumAccess = true; // But has premium from org + user.PersonalPremium = false; // No personal premium + user.OrganizationPremium = true; // But has premium from org sutProvider.GetDependency() - .GetCalculatedPremiumAsync(user.Id) + .GetPremiumAccessAsync(user.Id) .Returns(user); // Act @@ -130,39 +128,57 @@ public class HasPremiumAccessQueryTests [Theory, BitAutoData] public async Task HasPremiumFromOrganizationAsync_WhenUserHasOnlyPersonalPremium_ReturnsFalse( - UserWithCalculatedPremium user, + UserPremiumAccess user, SutProvider sutProvider) { // Arrange - user.Premium = true; // Has personal premium - user.HasPremiumAccess = true; + user.PersonalPremium = true; // Has personal premium + user.OrganizationPremium = false; // Not in any org that grants premium sutProvider.GetDependency() - .GetCalculatedPremiumAsync(user.Id) + .GetPremiumAccessAsync(user.Id) .Returns(user); // Act var result = await sutProvider.Sut.HasPremiumFromOrganizationAsync(user.Id); // Assert - Assert.False(result); // Should return false because premium is from personal, not org + Assert.False(result); // Should return false because user is not in an org that grants premium } [Theory, BitAutoData] - public async Task HasPremiumFromOrganizationAsync_WhenUserNotFound_ReturnsFalse( + public async Task HasPremiumFromOrganizationAsync_WhenUserHasBothPersonalAndOrgPremium_ReturnsTrue( + UserPremiumAccess user, + SutProvider sutProvider) + { + // Arrange + user.PersonalPremium = true; // Has personal premium + user.OrganizationPremium = true; // Also in an org that grants premium + + sutProvider.GetDependency() + .GetPremiumAccessAsync(user.Id) + .Returns(user); + + // Act + var result = await sutProvider.Sut.HasPremiumFromOrganizationAsync(user.Id); + + // Assert + Assert.True(result); // Should return true because user IS in an org that grants premium (regardless of personal premium) + } + + [Theory, BitAutoData] + public async Task HasPremiumFromOrganizationAsync_WhenUserNotFound_ThrowsNotFoundException( Guid userId, SutProvider sutProvider) { // Arrange sutProvider.GetDependency() - .GetCalculatedPremiumAsync(userId) - .Returns((UserWithCalculatedPremium)null); + .GetPremiumAccessAsync(userId) + .Returns(_ => throw new Bit.Core.Exceptions.NotFoundException()); - // Act - var result = await sutProvider.Sut.HasPremiumFromOrganizationAsync(userId); - - // Assert - Assert.False(result); + // Act & Assert + await Assert.ThrowsAsync( + () => sutProvider.Sut.HasPremiumFromOrganizationAsync(userId)); } [Theory, BitAutoData] @@ -173,8 +189,8 @@ public class HasPremiumAccessQueryTests var userIds = new List(); sutProvider.GetDependency() - .GetManyWithCalculatedPremiumAsync(userIds) - .Returns(new List()); + .GetPremiumAccessByIdsAsync(userIds) + .Returns(new List()); // Act var result = await sutProvider.Sut.HasPremiumAccessAsync(userIds); @@ -185,18 +201,21 @@ public class HasPremiumAccessQueryTests [Theory, BitAutoData] public async Task HasPremiumAccessAsync_Bulk_ReturnsCorrectStatus( - List users, + List users, SutProvider sutProvider) { // Arrange - users[0].HasPremiumAccess = true; - users[1].HasPremiumAccess = false; - users[2].HasPremiumAccess = true; + users[0].PersonalPremium = true; + users[0].OrganizationPremium = false; + users[1].PersonalPremium = false; + users[1].OrganizationPremium = false; + users[2].PersonalPremium = false; + users[2].OrganizationPremium = true; var userIds = users.Select(u => u.Id).ToList(); sutProvider.GetDependency() - .GetManyWithCalculatedPremiumAsync(userIds) + .GetPremiumAccessByIdsAsync(userIds) .Returns(users); // Act @@ -204,9 +223,9 @@ public class HasPremiumAccessQueryTests // Assert Assert.Equal(users.Count, result.Count); - Assert.True(result[users[0].Id]); - Assert.False(result[users[1].Id]); - Assert.True(result[users[2].Id]); + Assert.True(result[users[0].Id]); // Personal premium + Assert.False(result[users[1].Id]); // No premium + Assert.True(result[users[2].Id]); // Organization premium } }