diff --git a/src/Api/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Controllers/OrganizationSponsorshipsController.cs index 1c9326fc7..afc13c77d 100644 --- a/src/Api/Controllers/OrganizationSponsorshipsController.cs +++ b/src/Api/Controllers/OrganizationSponsorshipsController.cs @@ -5,7 +5,6 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Api; -using Bit.Core.Models.Api.Request; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Bit.Core.Services; @@ -24,20 +23,17 @@ namespace Bit.Api.Controllers private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ICurrentContext _currentContext; - private readonly IUserService _userService; public OrganizationSponsorshipsController(IOrganizationSponsorshipService organizationSponsorshipService, IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, - IUserService userService, ICurrentContext currentContext) { _organizationsSponsorshipService = organizationSponsorshipService; _organizationSponsorshipRepository = organizationSponsorshipRepository; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; - _userService = userService; _currentContext = currentContext; } @@ -45,98 +41,21 @@ namespace Bit.Api.Controllers [SelfHosted(NotSelfHostedOnly = true)] public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipRequestModel model) { - var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(model.PlanSponsorshipType)?.SponsoringProductType; - var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId); - if (requiredSponsoringProductType == null || - sponsoringOrg == null || - StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) - { - throw new BadRequestException("Specified Organization cannot sponsor other organizations."); - } - - var sponsoringOrgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default); - if (sponsoringOrgUser == null || sponsoringOrgUser.Status != OrganizationUserStatusType.Confirmed) - { - throw new BadRequestException("Only confirmed users can sponsor other organizations."); - } - - var existingOrgSponsorship = await _organizationSponsorshipRepository.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id); - if (existingOrgSponsorship != null) - { - throw new BadRequestException("Can only sponsor one organization per Organization User."); - } - - await _organizationsSponsorshipService.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, - model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName); + await _organizationsSponsorshipService.CreateSponsorshipAsync(sponsoringOrgId, model); } [HttpPost("{sponsoringOrgId}/families-for-enterprise/resend")] [SelfHosted(NotSelfHostedOnly = true)] public async Task ResendSponsorshipOffer(Guid sponsoringOrgId) { - var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId); - if (sponsoringOrg == null) - { - throw new BadRequestException("Cannot find the requested sponsoring organization."); - } - - var sponsoringOrgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default); - if (sponsoringOrgUser == null || sponsoringOrgUser.Status != OrganizationUserStatusType.Confirmed) - { - throw new BadRequestException("Only confirmed users can sponsor other organizations."); - } - - var existingOrgSponsorship = await _organizationSponsorshipRepository.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id); - if (existingOrgSponsorship == null || existingOrgSponsorship.OfferedToEmail == null) - { - throw new BadRequestException("Cannot find an outstanding sponsorship offer for this organization."); - } - - await _organizationsSponsorshipService.SendSponsorshipOfferAsync(sponsoringOrg, existingOrgSponsorship); + await _organizationsSponsorshipService.ResendSponsorshipOfferAsync(sponsoringOrgId); } [HttpPost("redeem")] [SelfHosted(NotSelfHostedOnly = true)] public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model) { - if (!await _organizationsSponsorshipService.ValidateRedemptionTokenAsync(sponsorshipToken)) - { - throw new BadRequestException("Failed to parse sponsorship token."); - } - - if (!await _currentContext.OrganizationOwner(model.SponsoredOrganizationId)) - { - throw new BadRequestException("Can only redeem sponsorship for an organization you own."); - } - var existingSponsorshipOffer = await _organizationSponsorshipRepository - .GetByOfferedToEmailAsync((await CurrentUser).Email); - if (existingSponsorshipOffer == null) - { - throw new BadRequestException("No unredeemed sponsorship offer exists for you."); - } - if ((await CurrentUser).Email != existingSponsorshipOffer.OfferedToEmail) - { - throw new BadRequestException("This sponsorship offer was issued to a different user email address."); - } - - var existingOrgSponsorship = await _organizationSponsorshipRepository - .GetBySponsoredOrganizationIdAsync(model.SponsoredOrganizationId); - if (existingOrgSponsorship != null) - { - throw new BadRequestException("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first."); - } - - // Check org to sponsor's product type - var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(model.PlanSponsorshipType)?.SponsoredProductType; - var organizationToSponsor = await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId); - if (requiredSponsoredProductType == null || - organizationToSponsor == null || - StaticStore.GetPlan(organizationToSponsor.PlanType).Product != requiredSponsoredProductType.Value) - { - throw new BadRequestException("Can only redeem sponsorship offer on families organizations."); - } - - await _organizationsSponsorshipService.SetUpSponsorshipAsync(existingSponsorshipOffer, organizationToSponsor); + await _organizationsSponsorshipService.RedeemSponsorshipAsync(sponsorshipToken, model); } [HttpDelete("{sponsoringOrganizationId}")] @@ -144,33 +63,7 @@ namespace Bit.Api.Controllers [SelfHosted(NotSelfHostedOnly = true)] public async Task RevokeSponsorship(Guid sponsoringOrganizationId) { - - var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrganizationId, _currentContext.UserId ?? default); - if (_currentContext.UserId != orgUser?.UserId) - { - throw new BadRequestException("Can only revoke a sponsorship you granted."); - } - - var existingOrgSponsorship = await _organizationSponsorshipRepository - .GetBySponsoringOrganizationUserIdAsync(orgUser.Id); - if (existingOrgSponsorship == null) - { - throw new BadRequestException("You are not currently sponsoring an organization."); - } - if (existingOrgSponsorship.SponsoredOrganizationId == null) - { - await _organizationSponsorshipRepository.DeleteAsync(existingOrgSponsorship); - return; - } - - var sponsoredOrganization = await _organizationRepository - .GetByIdAsync(existingOrgSponsorship.SponsoredOrganizationId.Value); - if (sponsoredOrganization == null) - { - throw new BadRequestException("Unable to find the sponsored Organization."); - } - - await _organizationsSponsorshipService.RemoveSponsorshipAsync(sponsoredOrganization, existingOrgSponsorship); + await _organizationsSponsorshipService.RevokeSponsorshipAsync(sponsoringOrganizationId); } [HttpDelete("sponsored/{sponsoredOrgId}")] @@ -179,29 +72,7 @@ namespace Bit.Api.Controllers public async Task RemoveSponsorship(Guid sponsoredOrgId) { - if (!await _currentContext.OrganizationOwner(sponsoredOrgId)) - { - throw new BadRequestException("Only the owner of an organization can remove sponsorship."); - } - - var existingOrgSponsorship = await _organizationSponsorshipRepository - .GetBySponsoredOrganizationIdAsync(sponsoredOrgId); - if (existingOrgSponsorship == null || existingOrgSponsorship.SponsoredOrganizationId == null) - { - throw new BadRequestException("The requested organization is not currently being sponsored."); - } - - var sponsoredOrganization = await _organizationRepository - .GetByIdAsync(existingOrgSponsorship.SponsoredOrganizationId.Value); - if (sponsoredOrganization == null) - { - throw new BadRequestException("Unable to find the sponsored Organization."); - } - - - await _organizationsSponsorshipService.RemoveSponsorshipAsync(sponsoredOrganization, existingOrgSponsorship); + _organizationsSponsorshipService.RemoveSponsorshipAsync(sponsoredOrgId); } - - private Task CurrentUser => _userService.GetUserByIdAsync(_currentContext.UserId.Value); } } diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationSponsorshipRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationSponsorshipRequestModel.cs index deb8d07f3..0fbb5dc0d 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationSponsorshipRequestModel.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationSponsorshipRequestModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; using Bit.Core.Utilities; -namespace Bit.Core.Models.Api.Request +namespace Bit.Core.Models.Api { public class OrganizationSponsorshipRequestModel { diff --git a/src/Core/Properties/AssemblyInfo.cs b/src/Core/Properties/AssemblyInfo.cs index 4c45d9af2..fe24e9094 100644 --- a/src/Core/Properties/AssemblyInfo.cs +++ b/src/Core/Properties/AssemblyInfo.cs @@ -1,5 +1,7 @@ using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.Extensions.Localization; [assembly: ResourceLocation("Resources")] [assembly: RootNamespace("Bit.Core")] +[assembly: InternalsVisibleTo("Bit.Core.Test")] diff --git a/src/Core/Services/IOrganizationSponsorshipService.cs b/src/Core/Services/IOrganizationSponsorshipService.cs index 4bb551f4c..e9a06f3e7 100644 --- a/src/Core/Services/IOrganizationSponsorshipService.cs +++ b/src/Core/Services/IOrganizationSponsorshipService.cs @@ -1,18 +1,18 @@ using System; using System.Threading.Tasks; using Bit.Core.Enums; +using Bit.Core.Models.Api; using Bit.Core.Models.Table; namespace Bit.Core.Services { public interface IOrganizationSponsorshipService { - Task ValidateRedemptionTokenAsync(string encryptedToken); - Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, - PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName); - Task SendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationSponsorship sponsorship); - Task SetUpSponsorshipAsync(OrganizationSponsorship sponsorship, Organization sponsoredOrganization); + Task CreateSponsorshipAsync(Guid sponsoringOrgId, OrganizationSponsorshipRequestModel model); Task ValidateSponsorshipAsync(Guid sponsoredOrganizationId); - Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship); + Task RedeemSponsorshipAsync(string sponsorshipToken, OrganizationSponsorshipRedeemRequestModel model); + Task ResendSponsorshipOfferAsync(Guid sponsoringOrgId); + Task RevokeSponsorshipAsync(Guid sponsoringOrganizationId); + Task RemoveSponsorshipAsync(Guid sponsoredOrgId); } } diff --git a/src/Core/Services/Implementations/OrganizationSponsorshipService.cs b/src/Core/Services/Implementations/OrganizationSponsorshipService.cs index 7c2f83772..f62a42e10 100644 --- a/src/Core/Services/Implementations/OrganizationSponsorshipService.cs +++ b/src/Core/Services/Implementations/OrganizationSponsorshipService.cs @@ -2,9 +2,12 @@ using System; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Utilities; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Microsoft.AspNetCore.DataProtection; +using Bit.Core.Context; +using Bit.Core.Models.Api; namespace Bit.Core.Services { @@ -16,27 +19,36 @@ namespace Bit.Core.Services private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository; private readonly IOrganizationRepository _organizationRepository; private readonly IUserRepository _userRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IPaymentService _paymentService; private readonly IMailService _mailService; + private readonly IUserService _userService; + private readonly ICurrentContext _currentContext; private readonly IDataProtector _dataProtector; public OrganizationSponsorshipService(IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationRepository organizationRepository, IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, IPaymentService paymentService, IMailService mailService, + IUserService userService, + ICurrentContext currentContext, IDataProtectionProvider dataProtectionProvider) { _organizationSponsorshipRepository = organizationSponsorshipRepository; _organizationRepository = organizationRepository; + _organizationUserRepository = organizationUserRepository; _userRepository = userRepository; _paymentService = paymentService; _mailService = mailService; + _userService = userService; + _currentContext = currentContext; _dataProtector = dataProtectionProvider.CreateProtector("OrganizationSponsorshipServiceDataProtector"); } - public async Task ValidateRedemptionTokenAsync(string encryptedToken) + internal async Task ValidateRedemptionTokenAsync(string encryptedToken) { if (!encryptedToken.StartsWith(TokenClearTextPrefix)) { @@ -53,7 +65,7 @@ namespace Bit.Core.Services if (dataParts[0].Equals(FamiliesForEnterpriseTokenName)) { - if (!Guid.TryParse(dataParts[1], out Guid sponsorshipId) || + if (!Guid.TryParse(dataParts[1], out var sponsorshipId) || !Enum.TryParse(dataParts[2], true, out var sponsorshipType)) { return false; @@ -71,13 +83,115 @@ namespace Bit.Core.Services return false; } + public async Task RevokeSponsorshipAsync(Guid sponsoringOrganizationId) + { + var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrganizationId, _currentContext.UserId ?? default); + if (_currentContext.UserId != orgUser?.UserId) + { + throw new BadRequestException("Can only revoke a sponsorship you granted."); + } + + var existingOrgSponsorship = await _organizationSponsorshipRepository + .GetBySponsoringOrganizationUserIdAsync(orgUser.Id); + if (existingOrgSponsorship == null) + { + throw new BadRequestException("You are not currently sponsoring an organization."); + } + if (existingOrgSponsorship.SponsoredOrganizationId == null) + { + await _organizationSponsorshipRepository.DeleteAsync(existingOrgSponsorship); + return; + } + + var sponsoredOrganization = await _organizationRepository + .GetByIdAsync(existingOrgSponsorship.SponsoredOrganizationId.Value); + if (sponsoredOrganization == null) + { + throw new BadRequestException("Unable to find the sponsored Organization."); + } + + await RemoveSponsorshipAsync(sponsoredOrganization, existingOrgSponsorship); + } + + public async Task CreateSponsorshipAsync(Guid sponsoringOrgId, OrganizationSponsorshipRequestModel model) + { + var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(model.PlanSponsorshipType)?.SponsoringProductType; + var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId); + if (requiredSponsoringProductType == null || + sponsoringOrg == null || + StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value) + { + throw new BadRequestException("Specified Organization cannot sponsor other organizations."); + } + + var sponsoringOrgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default); + if (sponsoringOrgUser == null || sponsoringOrgUser.Status != OrganizationUserStatusType.Confirmed) + { + throw new BadRequestException("Only confirmed users can sponsor other organizations."); + } + + var existingOrgSponsorship = await _organizationSponsorshipRepository.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id); + if (existingOrgSponsorship != null) + { + throw new BadRequestException("Can only sponsor one organization per Organization User."); + } + + await OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, + model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName); + } + + public async Task RedeemSponsorshipAsync(string sponsorshipToken, OrganizationSponsorshipRedeemRequestModel model) + { + if (!await ValidateRedemptionTokenAsync(sponsorshipToken)) + { + throw new BadRequestException("Failed to parse sponsorship token."); + } + + if (!await _currentContext.OrganizationOwner(model.SponsoredOrganizationId)) + { + throw new BadRequestException("Can only redeem sponsorship for an organization you own."); + } + var existingSponsorshipOffer = await _organizationSponsorshipRepository + .GetByOfferedToEmailAsync((await CurrentUser).Email); + if (existingSponsorshipOffer == null) + { + throw new BadRequestException("No unredeemed sponsorship offer exists for you."); + } + if ((await CurrentUser).Email != existingSponsorshipOffer.OfferedToEmail) + { + throw new BadRequestException("This sponsorship offer was issued to a different user email address."); + } + + var existingOrgSponsorship = await _organizationSponsorshipRepository + .GetBySponsoredOrganizationIdAsync(model.SponsoredOrganizationId); + if (existingOrgSponsorship != null) + { + throw new BadRequestException("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first."); + } + + // Check org to sponsor's product type + var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(model.PlanSponsorshipType)?.SponsoredProductType; + var organizationToSponsor = await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId); + if (requiredSponsoredProductType == null || + organizationToSponsor == null || + StaticStore.GetPlan(organizationToSponsor.PlanType).Product != requiredSponsoredProductType.Value) + { + throw new BadRequestException("Can only redeem sponsorship offer on families organizations."); + } + + await SetUpSponsorshipAsync(existingSponsorshipOffer, organizationToSponsor); + } + + private Task CurrentUser + => _userService.GetUserByIdAsync(_currentContext.UserId.Value); + private string RedemptionToken(Guid sponsorshipId, PlanSponsorshipType sponsorshipType) => string.Concat( TokenClearTextPrefix, _dataProtector.Protect($"{FamiliesForEnterpriseTokenName} {sponsorshipId} {sponsorshipType}") ); - public async Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, + internal async Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName) { var sponsorship = new OrganizationSponsorship @@ -106,7 +220,30 @@ namespace Bit.Core.Services } } - public async Task SendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationSponsorship sponsorship) + public async Task ResendSponsorshipOfferAsync(Guid sponsoringOrgId) + { + var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId); + if (sponsoringOrg == null) + { + throw new BadRequestException("Cannot find the requested sponsoring organization."); + } + + var sponsoringOrgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default); + if (sponsoringOrgUser == null || sponsoringOrgUser.Status != OrganizationUserStatusType.Confirmed) + { + throw new BadRequestException("Only confirmed users can sponsor other organizations."); + } + + var existingOrgSponsorship = await _organizationSponsorshipRepository.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id); + if (existingOrgSponsorship == null || existingOrgSponsorship.OfferedToEmail == null) + { + throw new BadRequestException("Cannot find an outstanding sponsorship offer for this organization."); + } + + await SendSponsorshipOfferAsync(sponsoringOrg, existingOrgSponsorship); + } + + internal async Task SendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationSponsorship sponsorship) { var user = await _userRepository.GetByEmailAsync(sponsorship.OfferedToEmail); var isExistingAccount = user != null; @@ -115,7 +252,7 @@ namespace Bit.Core.Services isExistingAccount, RedemptionToken(sponsorship.Id, sponsorship.PlanSponsorshipType.Value)); } - public async Task SetUpSponsorshipAsync(OrganizationSponsorship sponsorship, Organization sponsoredOrganization) + internal async Task SetUpSponsorshipAsync(OrganizationSponsorship sponsorship, Organization sponsoredOrganization) { if (sponsorship.PlanSponsorshipType == null) { @@ -173,7 +310,32 @@ namespace Bit.Core.Services return true; } - public async Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship = null) + public async Task RemoveSponsorshipAsync(Guid sponsoredOrgId) + { + if (!await _currentContext.OrganizationOwner(sponsoredOrgId)) + { + throw new BadRequestException("Only the owner of an organization can remove sponsorship."); + } + + var existingOrgSponsorship = await _organizationSponsorshipRepository + .GetBySponsoredOrganizationIdAsync(sponsoredOrgId); + if (existingOrgSponsorship == null || existingOrgSponsorship.SponsoredOrganizationId == null) + { + throw new BadRequestException("The requested organization is not currently being sponsored."); + } + + var sponsoredOrganization = await _organizationRepository + .GetByIdAsync(existingOrgSponsorship.SponsoredOrganizationId.Value); + if (sponsoredOrganization == null) + { + throw new BadRequestException("Unable to find the sponsored Organization."); + } + + + await RemoveSponsorshipAsync(sponsoredOrganization, existingOrgSponsorship); + } + + internal async Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship = null) { if (sponsoredOrganization != null) { diff --git a/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs b/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs deleted file mode 100644 index 8ddc65ab3..000000000 --- a/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs +++ /dev/null @@ -1,484 +0,0 @@ -using Xunit; -using Bit.Test.Common.AutoFixture.Attributes; -using System.Threading.Tasks; -using System; -using Bit.Core.Enums; -using System.Linq; -using System.Collections.Generic; -using Bit.Core.Models.Table; -using Bit.Test.Common.AutoFixture; -using Bit.Api.Controllers; -using Bit.Core.Context; -using NSubstitute; -using Bit.Core.Exceptions; -using Bit.Api.Test.AutoFixture.Attributes; -using Bit.Core.Repositories; -using Bit.Core.Models.Api.Request; -using Bit.Core.Services; -using Bit.Core.Models.Api; -using Bit.Core.Utilities; - -namespace Bit.Api.Test.Controllers -{ - [ControllerCustomize(typeof(OrganizationSponsorshipsController))] - [SutProviderCustomize] - public class OrganizationSponsorshipsControllerTests - { - public static IEnumerable EnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p }); - public static IEnumerable NonEnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p }); - public static IEnumerable NonFamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p }); - - [Theory] - [BitMemberAutoData(nameof(NonEnterprisePlanTypes))] - public async Task CreateSponsorship_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan, Organization org, - OrganizationSponsorshipRequestModel model, SutProvider sutProvider) - { - org.PlanType = sponsoringOrgPlan; - model.PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise; - - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateSponsorship(org.Id.ToString(), model)); - - Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .OfferSponsorshipAsync(default, default, default, default, default); - } - - public static IEnumerable NonConfirmedOrganizationUsersStatuses => - Enum.GetValues() - .Where(s => s != OrganizationUserStatusType.Confirmed) - .Select(s => new object[] { s }); - - [Theory] - [BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))] - public async Task CreateSponsorship_BadSponsoringUserStatus_ThrowsBadRequest( - OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser, - OrganizationSponsorshipRequestModel model, SutProvider sutProvider) - { - org.PlanType = PlanType.EnterpriseAnnually; - orgUser.Status = statusType; - - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) - .Returns(orgUser); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateSponsorship(org.Id.ToString(), model)); - - Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .OfferSponsorshipAsync(default, default, default, default, default); - } - - [Theory] - [BitAutoData] - public async Task CreateSponsorship_AlreadySponsoring_ThrowsBadRequest(Organization org, - OrganizationUser orgUser, OrganizationSponsorship sponsorship, - OrganizationSponsorshipRequestModel model, SutProvider sutProvider) - { - org.PlanType = PlanType.EnterpriseAnnually; - orgUser.Status = OrganizationUserStatusType.Confirmed; - - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) - .Returns(orgUser); - sutProvider.GetDependency() - .GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateSponsorship(org.Id.ToString(), model)); - - Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .OfferSponsorshipAsync(default, default, default, default, default); - } - - [Theory] - [BitAutoData] - public async Task ResendSponsorshipOffer_SponsoringOrgNotFound_ThrowsBadRequest(Guid sponsoringOrgId, - SutProvider sutProvider) - { - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ResendSponsorshipOffer(sponsoringOrgId.ToString())); - - Assert.Contains("Cannot find the requested sponsoring organization.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SendSponsorshipOfferAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task ResendSponsorshipOffer_SponsoringOrgUserNotFound_ThrowsBadRequest(Organization org, - SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ResendSponsorshipOffer(org.Id.ToString())); - - Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SendSponsorshipOfferAsync(default, default); - } - - [Theory] - [BitAutoData] - [BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))] - public async Task ResendSponsorshipOffer_SponsoringOrgUserNotConfirmed_ThrowsBadRequest(OrganizationUserStatusType status, - Organization org, OrganizationUser orgUser, - SutProvider sutProvider) - { - orgUser.Status = status; - - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) - .Returns(orgUser); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ResendSponsorshipOffer(org.Id.ToString())); - - Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SendSponsorshipOfferAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task ResendSponsorshipOffer_SponsorshipNotFound_ThrowsBadRequest(Organization org, - OrganizationUser orgUser, SutProvider sutProvider) - { - orgUser.Status = OrganizationUserStatusType.Confirmed; - - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) - .Returns(orgUser); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ResendSponsorshipOffer(org.Id.ToString())); - - Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SendSponsorshipOfferAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task ResendSponsorshipOffer_NoOfferToEmail_ThrowsBadRequest(Organization org, - OrganizationUser orgUser, OrganizationSponsorship sponsorship, - SutProvider sutProvider) - { - orgUser.Status = OrganizationUserStatusType.Confirmed; - sponsorship.OfferedToEmail = null; - - sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) - .Returns(orgUser); - sutProvider.GetDependency().GetBySponsoringOrganizationUserIdAsync(orgUser.Id) - .Returns(sponsorship); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ResendSponsorshipOffer(org.Id.ToString())); - - Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SendSponsorshipOfferAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RedeemSponsorship_BadToken_ThrowsBadRequest(string sponsorshipToken, - OrganizationSponsorshipRedeemRequestModel model, SutProvider sutProvider) - { - sutProvider.GetDependency().ValidateRedemptionTokenAsync(sponsorshipToken) - .Returns(false); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); - - Assert.Contains("Failed to parse sponsorship token.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SetUpSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, - OrganizationSponsorshipRedeemRequestModel model, SutProvider sutProvider) - { - sutProvider.GetDependency().ValidateRedemptionTokenAsync(sponsorshipToken) - .Returns(true); - sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(false); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); - - Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SetUpSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RedeemSponsorship_SponsorshipNotFound_ThrowsBadRequest(string sponsorshipToken, - OrganizationSponsorshipRedeemRequestModel model, User user, - SutProvider sutProvider) - { - sutProvider.GetDependency().ValidateRedemptionTokenAsync(sponsorshipToken) - .Returns(true); - sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); - sutProvider.GetDependency().GetByOfferedToEmailAsync(user.Email) - .Returns((OrganizationSponsorship)null); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); - - Assert.Contains("No unredeemed sponsorship offer exists for you.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SetUpSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RedeemSponsorship_OfferedToDifferentEmail_ThrowsBadRequest(string sponsorshipToken, - OrganizationSponsorshipRedeemRequestModel model, User user, OrganizationSponsorship sponsorship, - SutProvider sutProvider) - { - sutProvider.GetDependency().ValidateRedemptionTokenAsync(sponsorshipToken) - .Returns(true); - sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); - sutProvider.GetDependency().GetByOfferedToEmailAsync(user.Email) - .Returns(sponsorship); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); - - Assert.Contains("This sponsorship offer was issued to a different user email address.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SetUpSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RedeemSponsorship_OrgAlreadySponsored_ThrowsBadRequest(string sponsorshipToken, - OrganizationSponsorshipRedeemRequestModel model, User user, OrganizationSponsorship sponsorship, - OrganizationSponsorship existingSponsorship, SutProvider sutProvider) - { - user.Email = sponsorship.OfferedToEmail; - - sutProvider.GetDependency().ValidateRedemptionTokenAsync(sponsorshipToken) - .Returns(true); - sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); - sutProvider.GetDependency() - .GetByOfferedToEmailAsync(sponsorship.OfferedToEmail).Returns(sponsorship); - sutProvider.GetDependency() - .GetBySponsoredOrganizationIdAsync(model.SponsoredOrganizationId).Returns(existingSponsorship); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); - - Assert.Contains("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SetUpSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RedeemSponsorship_OrgNotFamiles_ThrowsBadRequest(PlanType planType, string sponsorshipToken, - OrganizationSponsorshipRedeemRequestModel model, User user, OrganizationSponsorship sponsorship, - Organization org, SutProvider sutProvider) - { - user.Email = sponsorship.OfferedToEmail; - org.PlanType = planType; - - sutProvider.GetDependency().ValidateRedemptionTokenAsync(sponsorshipToken) - .Returns(true); - sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); - sutProvider.GetDependency() - .GetByOfferedToEmailAsync(sponsorship.OfferedToEmail).Returns(sponsorship); - sutProvider.GetDependency() - .GetBySponsoredOrganizationIdAsync(model.SponsoredOrganizationId).Returns((OrganizationSponsorship)null); - sutProvider.GetDependency().GetByIdAsync(model.SponsoredOrganizationId).Returns(org); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); - - Assert.Contains("Can only redeem sponsorship offer on families organizations.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .SetUpSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RevokeSponsorship_WrongSponsoringUser_ThrowsBadRequest(OrganizationUser sponsoringOrgUser, - Guid currentUserId, SutProvider sutProvider) - { - sutProvider.GetDependency().UserId.Returns(currentUserId); - sutProvider.GetDependency().GetByIdAsync(sponsoringOrgUser.Id) - .Returns(sponsoringOrgUser); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id.ToString())); - - Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(OrganizationUser orgUser, - OrganizationSponsorship sponsorship, SutProvider sutProvider) - { - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(orgUser.OrganizationId, orgUser.UserId.Value) - .Returns(orgUser); - sutProvider.GetDependency() - .GetBySponsoringOrganizationUserIdAsync(Arg.Is(v => v != orgUser.Id)) - .Returns(sponsorship); - sutProvider.GetDependency() - .GetBySponsoringOrganizationUserIdAsync(orgUser.Id) - .Returns((OrganizationSponsorship)null); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RevokeSponsorship(orgUser.OrganizationId.ToString())); - - Assert.Contains("You are not currently sponsoring an organization.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RevokeSponsorship_SponsorshipNotRedeemed_ThrowsBadRequest(OrganizationUser orgUser, - OrganizationSponsorship sponsorship, SutProvider sutProvider) - { - sponsorship.SponsoredOrganizationId = null; - - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(orgUser.OrganizationId, orgUser.UserId.Value) - .Returns(orgUser); - sutProvider.GetDependency() - .GetBySponsoringOrganizationUserIdAsync(Arg.Is(v => v != orgUser.Id)) - .Returns(sponsorship); - sutProvider.GetDependency() - .GetBySponsoringOrganizationUserIdAsync(orgUser.Id) - .Returns((OrganizationSponsorship)sponsorship); - - await sutProvider.Sut.RevokeSponsorship(orgUser.OrganizationId.ToString()); - - await sutProvider.GetDependency().Received(1).DeleteAsync(sponsorship); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RevokeSponsorship_SponsoredOrgNotFound_ThrowsBadRequest(OrganizationUser orgUser, - OrganizationSponsorship sponsorship, SutProvider sutProvider) - { - - sutProvider.GetDependency().UserId.Returns(orgUser.UserId); - sutProvider.GetDependency().GetByOrganizationAsync(orgUser.OrganizationId, orgUser.UserId.Value) - .Returns(orgUser); - sutProvider.GetDependency() - .GetBySponsoringOrganizationUserIdAsync(orgUser.Id) - .Returns(sponsorship); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RevokeSponsorship(orgUser.OrganizationId.ToString())); - - Assert.Contains("Unable to find the sponsored Organization.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RemoveSponsorship_WrongOrgUserType_ThrowsBadRequest(Organization sponsoredOrg, - SutProvider sutProvider) - { - sutProvider.GetDependency().OrganizationOwner(Arg.Any()).Returns(false); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id.ToString())); - - Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RemoveSponsorship_NotSponsored_ThrowsBadRequest(Organization sponsoredOrg, - OrganizationSponsorship sponsorship, SutProvider sutProvider) - { - sutProvider.GetDependency().OrganizationOwner(Arg.Any()).Returns(true); - sutProvider.GetDependency() - .GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id) - .Returns((OrganizationSponsorship)null); - sutProvider.GetDependency() - .GetBySponsoredOrganizationIdAsync(Arg.Is(v => v != sponsoredOrg.Id)) - .Returns(sponsorship); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id.ToString())); - - Assert.Contains("The requested organization is not currently being sponsored.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - - [Theory] - [BitAutoData] - public async Task RemoveSponsorship_SponsoredOrgNotFound_ThrowsBadRequest(Organization sponsoredOrg, - OrganizationSponsorship sponsorship, SutProvider sutProvider) - { - sutProvider.GetDependency().OrganizationOwner(Arg.Any()).Returns(true); - sutProvider.GetDependency() - .GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id) - .Returns(sponsorship); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id.ToString())); - - Assert.Contains("Unable to find the sponsored Organization.", exception.Message); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .RemoveSponsorshipAsync(default, default); - } - } -} diff --git a/test/Core.Test/Services/OrganizationSponsorshipServiceTests.cs b/test/Core.Test/Services/OrganizationSponsorshipServiceTests.cs index cf43ed1df..cfdd308c9 100644 --- a/test/Core.Test/Services/OrganizationSponsorshipServiceTests.cs +++ b/test/Core.Test/Services/OrganizationSponsorshipServiceTests.cs @@ -2,7 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Context; using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Api; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Bit.Core.Services; @@ -20,7 +23,7 @@ namespace Bit.Core.Test.Services [SutProviderCustomize] public class OrganizationSponsorshipServiceTests { - private bool sponsorshipValidator(OrganizationSponsorship sponsorship, OrganizationSponsorship expectedSponsorship) + private static bool SponsorshipValidator(OrganizationSponsorship sponsorship, OrganizationSponsorship expectedSponsorship) { try { @@ -39,6 +42,14 @@ namespace Bit.Core.Test.Services public static IEnumerable NonEnterprisePlanTypes => Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p }); + public static IEnumerable NonFamiliesPlanTypes => + Enum.GetValues().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p }); + + public static IEnumerable NonConfirmedOrganizationUsersStatuses => + Enum.GetValues() + .Where(s => s != OrganizationUserStatusType.Confirmed) + .Select(s => new object[] { s }); + [Theory] [BitAutoData] public async Task OfferSponsorship_CreatesSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, @@ -69,7 +80,7 @@ namespace Bit.Core.Test.Services }; await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Is(s => sponsorshipValidator(s, expectedSponsorship))); + .CreateAsync(Arg.Is(s => SponsorshipValidator(s, expectedSponsorship))); await sutProvider.GetDependency().Received(1). SendFamiliesForEnterpriseOfferEmailAsync(sponsoredEmail, sponsoringOrg.Name, @@ -334,7 +345,7 @@ namespace Bit.Core.Test.Services [Theory] [BitAutoData] public async Task RemoveSponsorshipAsync_NullSponsoredOrg(OrganizationSponsorship sponsorship, - SutProvider sutProvider) + SutProvider sutProvider) { await sutProvider.Sut.RemoveSponsorshipAsync(null, sponsorship); @@ -345,7 +356,7 @@ namespace Bit.Core.Test.Services [Theory] [BitAutoData] public async Task RemoveSponsorshipAsync_NullSponsorship(Organization sponsoredOrg, - SutProvider sutProvider) + SutProvider sutProvider) { await sutProvider.Sut.RemoveSponsorshipAsync(sponsoredOrg, null); @@ -363,5 +374,458 @@ namespace Bit.Core.Test.Services await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, sponsorship, sutProvider); await AssertRemovedSponsorshipAsync(sponsorship, sutProvider); } + + [Theory] + [BitAutoData] + public async Task RemoveSponsorshipAsync_SponsoredOrgNotFound_ThrowsBadRequest(Organization sponsoredOrg, + OrganizationSponsorship sponsorship, SutProvider sutProvider) + { + sutProvider.GetDependency().OrganizationOwner(Arg.Any()).Returns(true); + sutProvider.GetDependency() + .GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id) + .Returns(sponsorship); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RemoveSponsorshipAsync(sponsoredOrg.Id)); + + Assert.Contains("Unable to find the sponsored Organization.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RemoveSponsorship_NotSponsored_ThrowsBadRequest(Organization sponsoredOrg, + OrganizationSponsorship sponsorship, SutProvider sutProvider) + { + sutProvider.GetDependency().OrganizationOwner(Arg.Any()).Returns(true); + sutProvider.GetDependency() + .GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id) + .Returns((OrganizationSponsorship)null); + sutProvider.GetDependency() + .GetBySponsoredOrganizationIdAsync(Arg.Is(v => v != sponsoredOrg.Id)) + .Returns(sponsorship); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RemoveSponsorshipAsync(sponsoredOrg.Id)); + + Assert.Contains("The requested organization is not currently being sponsored.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RemoveSponsorshipAsync_WrongOrgUserType_ThrowsBadRequest(Organization sponsoredOrg, + SutProvider sutProvider) + { + sutProvider.GetDependency().OrganizationOwner(Arg.Any()).Returns(false); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RemoveSponsorshipAsync(sponsoredOrg.Id)); + + Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RevokeSponsorshipAsync_SponsoredOrgNotFound_ThrowsBadRequest(OrganizationUser orgUser, + OrganizationSponsorship sponsorship, SutProvider sutProvider) + { + + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(orgUser.OrganizationId, orgUser.UserId.Value) + .Returns(orgUser); + sutProvider.GetDependency() + .GetBySponsoringOrganizationUserIdAsync(orgUser.Id) + .Returns(sponsorship); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RevokeSponsorshipAsync(orgUser.OrganizationId)); + + Assert.Contains("Unable to find the sponsored Organization.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RevokeSponsorshipAsync_SponsorshipNotRedeemed_ThrowsBadRequest(OrganizationUser orgUser, + OrganizationSponsorship sponsorship, SutProvider sutProvider) + { + sponsorship.SponsoredOrganizationId = null; + + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(orgUser.OrganizationId, orgUser.UserId.Value) + .Returns(orgUser); + sutProvider.GetDependency() + .GetBySponsoringOrganizationUserIdAsync(Arg.Is(v => v != orgUser.Id)) + .Returns(sponsorship); + sutProvider.GetDependency() + .GetBySponsoringOrganizationUserIdAsync(orgUser.Id) + .Returns((OrganizationSponsorship)sponsorship); + + await sutProvider.Sut.RevokeSponsorshipAsync(orgUser.OrganizationId); + + await sutProvider.GetDependency().Received(1).DeleteAsync(sponsorship); + + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RevokeSponsorshipAsync_NoExistingSponsorship_ThrowsBadRequest(OrganizationUser orgUser, + OrganizationSponsorship sponsorship, SutProvider sutProvider) + { + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(orgUser.OrganizationId, orgUser.UserId.Value) + .Returns(orgUser); + sutProvider.GetDependency() + .GetBySponsoringOrganizationUserIdAsync(Arg.Is(v => v != orgUser.Id)) + .Returns(sponsorship); + sutProvider.GetDependency() + .GetBySponsoringOrganizationUserIdAsync(orgUser.Id) + .Returns((OrganizationSponsorship)null); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RevokeSponsorshipAsync(orgUser.OrganizationId)); + + Assert.Contains("You are not currently sponsoring an organization.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RevokeSponsorshipAsync_WrongSponsoringUser_ThrowsBadRequest(OrganizationUser sponsoringOrgUser, + Guid currentUserId, SutProvider sutProvider) + { + sutProvider.GetDependency().UserId.Returns(currentUserId); + sutProvider.GetDependency().GetByIdAsync(sponsoringOrgUser.Id) + .Returns(sponsoringOrgUser); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RevokeSponsorshipAsync(sponsoringOrgUser.Id)); + + Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .RemoveSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RedeemSponsorshipAsync_OrgNotFamiles_ThrowsBadRequest(PlanType planType, string sponsorshipToken, + OrganizationSponsorshipRedeemRequestModel model, User user, OrganizationSponsorship sponsorship, + Organization org, SutProvider sutProvider) + { + user.Email = sponsorship.OfferedToEmail; + org.PlanType = planType; + + sutProvider.Sut.ValidateRedemptionTokenAsync(sponsorshipToken) + .Returns(true); + sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); + sutProvider.GetDependency().UserId.Returns(user.Id); + sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); + sutProvider.GetDependency() + .GetByOfferedToEmailAsync(sponsorship.OfferedToEmail).Returns(sponsorship); + sutProvider.GetDependency() + .GetBySponsoredOrganizationIdAsync(model.SponsoredOrganizationId).Returns((OrganizationSponsorship)null); + sutProvider.GetDependency().GetByIdAsync(model.SponsoredOrganizationId).Returns(org); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RedeemSponsorshipAsync(sponsorshipToken, model)); + + Assert.Contains("Can only redeem sponsorship offer on families organizations.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SetUpSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RedeemSponsorshipAsync_OrgAlreadySponsored_ThrowsBadRequest(string sponsorshipToken, + OrganizationSponsorshipRedeemRequestModel model, User user, OrganizationSponsorship sponsorship, + OrganizationSponsorship existingSponsorship, SutProvider sutProvider) + { + user.Email = sponsorship.OfferedToEmail; + + sutProvider.Sut.ValidateRedemptionTokenAsync(sponsorshipToken) + .Returns(true); + sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); + sutProvider.GetDependency().UserId.Returns(user.Id); + sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); + sutProvider.GetDependency() + .GetByOfferedToEmailAsync(sponsorship.OfferedToEmail).Returns(sponsorship); + sutProvider.GetDependency() + .GetBySponsoredOrganizationIdAsync(model.SponsoredOrganizationId).Returns(existingSponsorship); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RedeemSponsorshipAsync(sponsorshipToken, model)); + + Assert.Contains("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SetUpSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RedeemSponsorshipAsync_OfferedToDifferentEmail_ThrowsBadRequest(string sponsorshipToken, + OrganizationSponsorshipRedeemRequestModel model, User user, OrganizationSponsorship sponsorship, + SutProvider sutProvider) + { + sutProvider.Sut.ValidateRedemptionTokenAsync(sponsorshipToken) + .Returns(true); + sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); + sutProvider.GetDependency().UserId.Returns(user.Id); + sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); + sutProvider.GetDependency().GetByOfferedToEmailAsync(user.Email) + .Returns(sponsorship); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RedeemSponsorshipAsync(sponsorshipToken, model)); + + Assert.Contains("This sponsorship offer was issued to a different user email address.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SetUpSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RedeemSponsorshipAsync_SponsorshipNotFound_ThrowsBadRequest(string sponsorshipToken, + OrganizationSponsorshipRedeemRequestModel model, User user, + SutProvider sutProvider) + { + sutProvider.Sut.ValidateRedemptionTokenAsync(sponsorshipToken) + .Returns(true); + sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true); + sutProvider.GetDependency().UserId.Returns(user.Id); + sutProvider.GetDependency().GetUserByIdAsync(user.Id).Returns(user); + sutProvider.GetDependency().GetByOfferedToEmailAsync(user.Email) + .Returns((OrganizationSponsorship)null); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RedeemSponsorshipAsync(sponsorshipToken, model)); + + Assert.Contains("No unredeemed sponsorship offer exists for you.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SetUpSponsorshipAsync(default, default); + } + + + [Theory] + [BitMemberAutoData(nameof(NonEnterprisePlanTypes))] + public async Task CreateSponsorshipAsync_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan, Organization org, + OrganizationSponsorshipRequestModel model, SutProvider sutProvider) + { + org.PlanType = sponsoringOrgPlan; + model.PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateSponsorshipAsync(org.Id, model)); + + Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .OfferSponsorshipAsync(default, default, default, default, default); + } + + [Theory] + [BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))] + public async Task CreateSponsorship_BadSponsoringUserStatus_ThrowsBadRequest( + OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser, + OrganizationSponsorshipRequestModel model, SutProvider sutProvider) + { + org.PlanType = PlanType.EnterpriseAnnually; + orgUser.Status = statusType; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) + .Returns(orgUser); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateSponsorshipAsync(org.Id, model)); + + Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .OfferSponsorshipAsync(default, default, default, default, default); + } + + [Theory] + [BitAutoData] + public async Task CreateSponsorshipAsync_AlreadySponsoring_ThrowsBadRequest(Organization org, + OrganizationUser orgUser, OrganizationSponsorship sponsorship, + OrganizationSponsorshipRequestModel model, SutProvider sutProvider) + { + org.PlanType = PlanType.EnterpriseAnnually; + orgUser.Status = OrganizationUserStatusType.Confirmed; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) + .Returns(orgUser); + sutProvider.GetDependency() + .GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateSponsorshipAsync(org.Id, model)); + + Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .OfferSponsorshipAsync(default, default, default, default, default); + } + + [Theory] + [BitAutoData] + public async Task ResendSponsorshipOfferAsync_SponsoringOrgNotFound_ThrowsBadRequest(Guid sponsoringOrgId, + SutProvider sutProvider) + { + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ResendSponsorshipOfferAsync(sponsoringOrgId)); + + Assert.Contains("Cannot find the requested sponsoring organization.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SendSponsorshipOfferAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task ResendSponsorshipOfferAsync_SponsoringOrgUserNotFound_ThrowsBadRequest(Organization org, + SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ResendSponsorshipOfferAsync(org.Id)); + + Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SendSponsorshipOfferAsync(default, default); + } + + [Theory] + [BitAutoData] + [BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))] + public async Task ResendSponsorshipOfferAsync_SponsoringOrgUserNotConfirmed_ThrowsBadRequest(OrganizationUserStatusType status, + Organization org, OrganizationUser orgUser, + SutProvider sutProvider) + { + orgUser.Status = status; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) + .Returns(orgUser); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ResendSponsorshipOfferAsync(org.Id)); + + Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SendSponsorshipOfferAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task ResendSponsorshipOfferAsync_SponsorshipNotFound_ThrowsBadRequest(Organization org, + OrganizationUser orgUser, SutProvider sutProvider) + { + orgUser.Status = OrganizationUserStatusType.Confirmed; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) + .Returns(orgUser); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ResendSponsorshipOfferAsync(org.Id)); + + Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SendSponsorshipOfferAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task ResendSponsorshipOfferAsync_NoOfferToEmail_ThrowsBadRequest(Organization org, + OrganizationUser orgUser, OrganizationSponsorship sponsorship, + SutProvider sutProvider) + { + orgUser.Status = OrganizationUserStatusType.Confirmed; + sponsorship.OfferedToEmail = null; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().UserId.Returns(orgUser.UserId); + sutProvider.GetDependency().GetByOrganizationAsync(org.Id, orgUser.UserId.Value) + .Returns(orgUser); + sutProvider.GetDependency().GetBySponsoringOrganizationUserIdAsync(orgUser.Id) + .Returns(sponsorship); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ResendSponsorshipOfferAsync(org.Id)); + + Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SendSponsorshipOfferAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RedeemSponsorshipAsync_BadToken_ThrowsBadRequest(string sponsorshipToken, + OrganizationSponsorshipRedeemRequestModel model, SutProvider sutProvider) + { + sutProvider.Sut + .ValidateRedemptionTokenAsync(sponsorshipToken) + .Returns(false); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RedeemSponsorshipAsync(sponsorshipToken, model)); + + Assert.Contains("Failed to parse sponsorship token.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SetUpSponsorshipAsync(default, default); + } + + [Theory] + [BitAutoData] + public async Task RedeemSponsorshipAsync_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, + OrganizationSponsorshipRedeemRequestModel model, SutProvider sutProvider) + { + sutProvider.Sut + .ValidateRedemptionTokenAsync(sponsorshipToken) + .Returns(true); + sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(false); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.RedeemSponsorshipAsync(sponsorshipToken, model)); + + Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message); + await sutProvider.Sut + .DidNotReceiveWithAnyArgs() + .SetUpSponsorshipAsync(default, default); + } } }