using System.Security.Claims; using Bit.Api.AdminConsole.Controllers; using Bit.Api.Billing.Models.Requests; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Providers.Services; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; namespace Bit.Api.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(ProviderClientsController))] [SutProviderCustomize] public class ProviderClientsControllerTests { [Theory, BitAutoData] public async Task TryGetBillableProviderAsync_ProviderNotFound_ReturnsNotFound( Guid providerId, CreateClientOrganizationRequestBody requestBody, SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(providerId).ReturnsNull(); var result = await sutProvider.Sut.CreateAsync(providerId, requestBody); AssertNotFound(result); } [Theory, BitAutoData] public async Task TryGetBillableProviderAsync_ProviderNotBillable_ReturnsUnauthorized( Provider provider, CreateClientOrganizationRequestBody requestBody, SutProvider sutProvider) { provider.Status = ProviderStatusType.Created; sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); var result = await sutProvider.Sut.CreateAsync(provider.Id, requestBody); AssertUnauthorized(result); } [Theory, BitAutoData] public async Task TryGetBillableProviderAsync_StripeNotEnabled_ReturnsInternalError( Provider provider, CreateClientOrganizationRequestBody requestBody, SutProvider sutProvider) { provider.Type = ProviderType.Msp; provider.Status = ProviderStatusType.Billable; provider.GatewayCustomerId = null; provider.GatewaySubscriptionId = null; sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); var result = await sutProvider.Sut.CreateAsync(provider.Id, requestBody); var response = Assert.IsType>(result); Assert.Equal(StatusCodes.Status500InternalServerError, response.StatusCode); } [Theory, BitAutoData] public async Task CreateAsync_NoPrincipalUser_Unauthorized( Provider provider, CreateClientOrganizationRequestBody requestBody, SutProvider sutProvider) { ConfigureStableProviderInputs(provider, sutProvider); sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).ReturnsNull(); var result = await sutProvider.Sut.CreateAsync(provider.Id, requestBody); AssertUnauthorized(result); } [Theory, BitAutoData] public async Task CreateAsync_OK( Provider provider, CreateClientOrganizationRequestBody requestBody, SutProvider sutProvider) { ConfigureStableProviderInputs(provider, sutProvider); var user = new User(); sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()) .Returns(user); var clientOrganizationId = Guid.NewGuid(); sutProvider.GetDependency().CreateOrganizationAsync( provider.Id, Arg.Is(signup => signup.Name == requestBody.Name && signup.Plan == requestBody.PlanType && signup.AdditionalSeats == requestBody.Seats && signup.OwnerKey == requestBody.Key && signup.Keys.PublicKey == requestBody.KeyPair.PublicKey && signup.Keys.WrappedPrivateKey == requestBody.KeyPair.EncryptedPrivateKey && signup.CollectionName == requestBody.CollectionName), requestBody.OwnerEmail, user) .Returns(new ProviderOrganization { OrganizationId = clientOrganizationId }); var clientOrganization = new Organization { Id = clientOrganizationId }; sutProvider.GetDependency().GetByIdAsync(clientOrganizationId) .Returns(clientOrganization); var result = await sutProvider.Sut.CreateAsync(provider.Id, requestBody); Assert.IsType(result); await sutProvider.GetDependency().Received(1).CreateCustomerForClientOrganization( provider, clientOrganization); } [Theory, BitAutoData] public async Task AddExistingOrganizationAsync_ServiceUser_Unauthorized( Provider provider, AddExistingOrganizationRequestBody requestBody, SutProvider sutProvider) { ConfigureStableProviderInputs(provider, sutProvider); var result = await sutProvider.Sut.AddExistingOrganizationAsync(provider.Id, requestBody); AssertUnauthorized(result); } [Theory, BitAutoData] public async Task AddExistingOrganizationAsync_NotOrgOwner_Unauthorized( Provider provider, AddExistingOrganizationRequestBody requestBody, SutProvider sutProvider) { ConfigureStableProviderInputs(provider, sutProvider); sutProvider.GetDependency().OrganizationOwner(requestBody.OrganizationId) .Returns(false); var result = await sutProvider.Sut.AddExistingOrganizationAsync(provider.Id, requestBody); AssertUnauthorized(result); } [Theory, BitAutoData] public async Task AddExistingOrganizationAsync_OrgNotAddable_NotFound( Provider provider, AddExistingOrganizationRequestBody requestBody, Guid userId, SutProvider sutProvider) { ConfigureStableProviderInputs(provider, sutProvider); sutProvider.GetDependency().OrganizationOwner(requestBody.OrganizationId) .Returns(true); sutProvider.GetDependency().UserId.Returns(userId); sutProvider.GetDependency() .GetAddableToProviderByUserIdAsync(userId, provider.Type) .Returns([]); var result = await sutProvider.Sut.AddExistingOrganizationAsync(provider.Id, requestBody); AssertNotFound(result); } [Theory, BitAutoData] public async Task AddExistingOrganizationAsync_Ok( Provider provider, AddExistingOrganizationRequestBody requestBody, Organization organization, Guid userId, SutProvider sutProvider) { organization.Id = requestBody.OrganizationId; ConfigureStableProviderInputs(provider, sutProvider); sutProvider.GetDependency().OrganizationOwner(requestBody.OrganizationId) .Returns(true); sutProvider.GetDependency().UserId.Returns(userId); sutProvider.GetDependency() .GetAddableToProviderByUserIdAsync(userId, provider.Type) .Returns([organization]); var result = await sutProvider.Sut.AddExistingOrganizationAsync(provider.Id, requestBody); await sutProvider.GetDependency().Received(1) .AddExistingOrganization(provider, organization, requestBody.Key); Assert.IsType(result); } [Theory, BitAutoData] public async Task UpdateAsync_ServiceUserMakingPurchase_Unauthorized( Provider provider, Guid providerOrganizationId, UpdateClientOrganizationRequestBody requestBody, ProviderOrganization providerOrganization, Organization organization, SutProvider sutProvider) { organization.PlanType = PlanType.TeamsMonthly; organization.Seats = 10; organization.Status = OrganizationStatusType.Managed; requestBody.AssignedSeats = 20; providerOrganization.ProviderId = provider.Id; ConfigureStableProviderInputs(provider, sutProvider); sutProvider.GetDependency().GetByIdAsync(providerOrganizationId) .Returns(providerOrganization); sutProvider.GetDependency().GetByIdAsync(providerOrganization.OrganizationId) .Returns(organization); sutProvider.GetDependency().ProviderProviderAdmin(provider.Id).Returns(false); sutProvider.GetDependency().SeatAdjustmentResultsInPurchase( provider, PlanType.TeamsMonthly, 10).Returns(true); var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody); var response = (JsonHttpResult)result; Assert.Equal(StatusCodes.Status401Unauthorized, response.StatusCode); Assert.Equal("Service users cannot purchase additional seats.", response.Value.Message); } [Theory, BitAutoData] public async Task UpdateAsync_ProviderOrganizationBelongsToDifferentProvider_NotFound( Provider provider, Guid providerOrganizationId, UpdateClientOrganizationRequestBody requestBody, ProviderOrganization providerOrganization, SutProvider sutProvider) { ConfigureStableProviderInputs(provider, sutProvider); providerOrganization.ProviderId = Guid.NewGuid(); sutProvider.GetDependency().GetByIdAsync(providerOrganizationId) .Returns(providerOrganization); var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody); AssertNotFound(result); } [Theory, BitAutoData] public async Task UpdateAsync_Ok( Provider provider, Guid providerOrganizationId, UpdateClientOrganizationRequestBody requestBody, ProviderOrganization providerOrganization, Organization organization, SutProvider sutProvider) { organization.PlanType = PlanType.TeamsMonthly; organization.Seats = 10; organization.Status = OrganizationStatusType.Managed; requestBody.AssignedSeats = 20; providerOrganization.ProviderId = provider.Id; ConfigureStableProviderInputs(provider, sutProvider); sutProvider.GetDependency().GetByIdAsync(providerOrganizationId) .Returns(providerOrganization); sutProvider.GetDependency().GetByIdAsync(providerOrganization.OrganizationId) .Returns(organization); sutProvider.GetDependency().ProviderProviderAdmin(provider.Id).Returns(false); sutProvider.GetDependency().SeatAdjustmentResultsInPurchase( provider, PlanType.TeamsMonthly, 10).Returns(false); var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody); await sutProvider.GetDependency().Received(1) .ScaleSeats( provider, PlanType.TeamsMonthly, 10); await sutProvider.GetDependency().Received(1) .ReplaceAsync(Arg.Is(org => org.Seats == requestBody.AssignedSeats && org.Name == requestBody.Name)); Assert.IsType(result); } private static void ConfigureStableProviderInputs( Provider provider, SutProvider sutProvider) { provider.Type = ProviderType.Msp; provider.Status = ProviderStatusType.Billable; sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); } private static void AssertUnauthorized(IResult result) { Assert.IsType(result); } private static void AssertNotFound(IResult result) { Assert.IsType>(result); var response = ((NotFound)result).Value; Assert.Equal("Resource not found.", response.Message); } }