mirror of
https://github.com/bitwarden/server.git
synced 2025-12-10 00:42:07 -06:00
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.
This commit is contained in:
parent
0a42c9e5d7
commit
651d5e3aac
@ -3,10 +3,6 @@ using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.Billing.Premium.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Query for checking premium access status for users using the existing stored procedure
|
||||
/// that calculates premium access from personal subscriptions and organization memberships.
|
||||
/// </summary>
|
||||
public class HasPremiumAccessQuery : IHasPremiumAccessQuery
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
@ -18,17 +14,18 @@ public class HasPremiumAccessQuery : IHasPremiumAccessQuery
|
||||
|
||||
public async Task<bool> 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<Dictionary<Guid, bool>> HasPremiumAccessAsync(IEnumerable<Guid> 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<bool> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,38 +1,30 @@
|
||||
namespace Bit.Core.Billing.Premium.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
///
|
||||
/// <para>
|
||||
/// <strong>Note:</strong> This is different from checking User.Premium, which only indicates
|
||||
/// personal subscription status. Use these methods to check actual premium feature access.
|
||||
/// </para>
|
||||
/// Centralized query for checking if users have premium access through personal subscriptions or organizations.
|
||||
/// Note: Different from User.Premium which only checks personal subscriptions.
|
||||
/// </summary>
|
||||
public interface IHasPremiumAccessQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to check for premium access</param>
|
||||
/// <returns>True if user can access premium features; false otherwise</returns>
|
||||
/// <param name="userId">The user ID to check</param>
|
||||
/// <returns>True if user can access premium features</returns>
|
||||
Task<bool> HasPremiumAccessAsync(Guid userId);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to check for organization premium access</param>
|
||||
/// <returns>True if user has premium access through any organization; false otherwise</returns>
|
||||
Task<bool> HasPremiumFromOrganizationAsync(Guid userId);
|
||||
/// <param name="userIds">The user IDs to check</param>
|
||||
/// <returns>Dictionary mapping user IDs to their premium access status</returns>
|
||||
Task<Dictionary<Guid, bool>> HasPremiumAccessAsync(IEnumerable<Guid> userIds);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="userIds">The user IDs to check for premium access</param>
|
||||
/// <returns>Dictionary mapping user IDs to their premium access status (personal or through organization)</returns>
|
||||
Task<Dictionary<Guid, bool>> HasPremiumAccessAsync(IEnumerable<Guid> userIds);
|
||||
/// <param name="userId">The user ID to check</param>
|
||||
/// <returns>True if user is in any organization that grants premium</returns>
|
||||
Task<bool> HasPremiumFromOrganizationAsync(Guid userId);
|
||||
}
|
||||
|
||||
@ -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<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = true;
|
||||
user.HasPremiumAccess = true;
|
||||
user.PersonalPremium = true;
|
||||
user.OrganizationPremium = false;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
user.HasPremiumAccess = true; // Has org premium
|
||||
user.PersonalPremium = false;
|
||||
user.OrganizationPremium = true; // Has org premium
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
user.HasPremiumAccess = false;
|
||||
user.PersonalPremium = false;
|
||||
user.OrganizationPremium = false;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetCalculatedPremiumAsync(userId)
|
||||
.Returns((UserWithCalculatedPremium)null);
|
||||
.GetPremiumAccessAsync(userId)
|
||||
.Returns<UserPremiumAccess>(_ => throw new Bit.Core.Exceptions.NotFoundException());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.HasPremiumAccessAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Bit.Core.Exceptions.NotFoundException>(
|
||||
() => sutProvider.Sut.HasPremiumAccessAsync(userId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HasPremiumFromOrganizationAsync_WhenUserHasNoOrganizations_ReturnsFalse(
|
||||
UserWithCalculatedPremium user,
|
||||
UserPremiumAccess user,
|
||||
SutProvider<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
user.HasPremiumAccess = false; // No premium from anywhere
|
||||
user.PersonalPremium = false;
|
||||
user.OrganizationPremium = false; // No premium from anywhere
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> 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<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> 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<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.PersonalPremium = true; // Has personal premium
|
||||
user.OrganizationPremium = true; // Also in an org that grants premium
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.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<HasPremiumAccessQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetCalculatedPremiumAsync(userId)
|
||||
.Returns((UserWithCalculatedPremium)null);
|
||||
.GetPremiumAccessAsync(userId)
|
||||
.Returns<UserPremiumAccess>(_ => throw new Bit.Core.Exceptions.NotFoundException());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.HasPremiumFromOrganizationAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Bit.Core.Exceptions.NotFoundException>(
|
||||
() => sutProvider.Sut.HasPremiumFromOrganizationAsync(userId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -173,8 +189,8 @@ public class HasPremiumAccessQueryTests
|
||||
var userIds = new List<Guid>();
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyWithCalculatedPremiumAsync(userIds)
|
||||
.Returns(new List<UserWithCalculatedPremium>());
|
||||
.GetPremiumAccessByIdsAsync(userIds)
|
||||
.Returns(new List<UserPremiumAccess>());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.HasPremiumAccessAsync(userIds);
|
||||
@ -185,18 +201,21 @@ public class HasPremiumAccessQueryTests
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HasPremiumAccessAsync_Bulk_ReturnsCorrectStatus(
|
||||
List<UserWithCalculatedPremium> users,
|
||||
List<UserPremiumAccess> users,
|
||||
SutProvider<HasPremiumAccessQuery> 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<IUserRepository>()
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user