2022-06-30 01:46:41 +02:00
using System.Text.Json ;
2022-05-10 23:12:09 +02:00
using Bit.Api.Controllers ;
using Bit.Api.Models.Request.Organizations ;
using Bit.Api.Models.Response.Organizations ;
using Bit.Core.Context ;
using Bit.Core.Entities ;
using Bit.Core.Enums ;
using Bit.Core.Exceptions ;
using Bit.Core.Models.Business ;
using Bit.Core.Models.OrganizationConnectionConfigs ;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces ;
using Bit.Core.Repositories ;
using Bit.Core.Services ;
using Bit.Core.Settings ;
using Bit.Test.Common.AutoFixture ;
using Bit.Test.Common.AutoFixture.Attributes ;
using Bit.Test.Common.Helpers ;
using NSubstitute ;
using Xunit ;
namespace Bit.Api.Test.Controllers
{
[ControllerCustomize(typeof(OrganizationConnectionsController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class OrganizationConnectionsControllerTests
{
public static IEnumerable < object [ ] > ConnectionTypes = >
Enum . GetValues < OrganizationConnectionType > ( ) . Select ( p = > new object [ ] { p } ) ;
[Theory]
[BitAutoData(true, true)]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(false, false)]
public void ConnectionEnabled_RequiresBothSelfHostAndCommunications ( bool selfHosted , bool enableCloudCommunication , SutProvider < OrganizationConnectionsController > sutProvider )
{
var globalSettingsMock = sutProvider . GetDependency < IGlobalSettings > ( ) ;
globalSettingsMock . SelfHosted . Returns ( selfHosted ) ;
globalSettingsMock . EnableCloudCommunication . Returns ( enableCloudCommunication ) ;
Action < bool > assert = selfHosted & & enableCloudCommunication ? Assert . True : Assert . False ;
var result = sutProvider . Sut . ConnectionsEnabled ( ) ;
assert ( result ) ;
}
[Theory]
[BitAutoData]
2022-07-14 21:58:48 +02:00
public async Task CreateConnection_CloudBillingSync_RequiresOwnerPermissions ( SutProvider < OrganizationConnectionsController > sutProvider )
2022-05-10 23:12:09 +02:00
{
2022-07-14 21:58:48 +02:00
var model = new OrganizationConnectionRequestModel
{
Type = OrganizationConnectionType . CloudBillingSync ,
} ;
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = > sutProvider . Sut . CreateConnection ( model ) ) ;
2022-05-10 23:12:09 +02:00
2022-07-14 21:58:48 +02:00
Assert . Contains ( $"You do not have permission to create a connection of type" , exception . Message ) ;
2022-05-10 23:12:09 +02:00
}
[Theory]
[BitMemberAutoData(nameof(ConnectionTypes))]
public async Task CreateConnection_OnlyOneConnectionOfEachType ( OrganizationConnectionType type ,
OrganizationConnectionRequestModel model , BillingSyncConfig config , Guid existingEntityId ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
model . Type = type ;
model . Config = JsonDocumentFromObject ( config ) ;
var typedModel = new OrganizationConnectionRequestModel < BillingSyncConfig > ( model ) ;
var existing = typedModel . ToData ( existingEntityId ) . ToEntity ( ) ;
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( model . OrganizationId ) . Returns ( true ) ;
sutProvider . GetDependency < IOrganizationConnectionRepository > ( ) . GetByOrganizationIdTypeAsync ( model . OrganizationId , type ) . Returns ( new [ ] { existing } ) ;
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = > sutProvider . Sut . CreateConnection ( model ) ) ;
Assert . Contains ( $"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization." , exception . Message ) ;
}
2022-05-16 15:57:00 +02:00
[Theory]
[BitAutoData]
public async Task CreateConnection_BillingSyncType_InvalidLicense_Throws ( OrganizationConnectionRequestModel model ,
BillingSyncConfig config , Guid cloudOrgId , OrganizationLicense organizationLicense ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
model . Type = OrganizationConnectionType . CloudBillingSync ;
organizationLicense . Id = cloudOrgId ;
model . Config = JsonDocumentFromObject ( config ) ;
var typedModel = new OrganizationConnectionRequestModel < BillingSyncConfig > ( model ) ;
typedModel . ParsedConfig . CloudOrganizationId = cloudOrgId ;
sutProvider . GetDependency < ICurrentContext > ( )
. OrganizationOwner ( model . OrganizationId )
. Returns ( true ) ;
sutProvider . GetDependency < ILicensingService > ( )
. ReadOrganizationLicenseAsync ( model . OrganizationId )
. Returns ( organizationLicense ) ;
sutProvider . GetDependency < ILicensingService > ( )
. VerifyLicense ( organizationLicense )
. Returns ( false ) ;
await Assert . ThrowsAsync < BadRequestException > ( async ( ) = > await sutProvider . Sut . CreateConnection ( model ) ) ;
}
2022-05-10 23:12:09 +02:00
[Theory]
[BitAutoData]
public async Task CreateConnection_Success ( OrganizationConnectionRequestModel model , BillingSyncConfig config ,
2022-05-16 15:57:00 +02:00
Guid cloudOrgId , OrganizationLicense organizationLicense , SutProvider < OrganizationConnectionsController > sutProvider )
2022-05-10 23:12:09 +02:00
{
2022-05-16 15:57:00 +02:00
organizationLicense . Id = cloudOrgId ;
2022-05-10 23:12:09 +02:00
model . Config = JsonDocumentFromObject ( config ) ;
var typedModel = new OrganizationConnectionRequestModel < BillingSyncConfig > ( model ) ;
typedModel . ParsedConfig . CloudOrganizationId = cloudOrgId ;
2022-07-14 21:58:48 +02:00
sutProvider . GetDependency < IGlobalSettings > ( ) . SelfHosted . Returns ( true ) ;
2022-05-10 23:12:09 +02:00
sutProvider . GetDependency < ICreateOrganizationConnectionCommand > ( ) . CreateAsync < BillingSyncConfig > ( default )
. ReturnsForAnyArgs ( typedModel . ToData ( Guid . NewGuid ( ) ) . ToEntity ( ) ) ;
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( model . OrganizationId ) . Returns ( true ) ;
sutProvider . GetDependency < ILicensingService > ( )
. ReadOrganizationLicenseAsync ( Arg . Any < Guid > ( ) )
2022-05-16 15:57:00 +02:00
. Returns ( organizationLicense ) ;
sutProvider . GetDependency < ILicensingService > ( )
. VerifyLicense ( organizationLicense )
. Returns ( true ) ;
2022-05-10 23:12:09 +02:00
await sutProvider . Sut . CreateConnection ( model ) ;
await sutProvider . GetDependency < ICreateOrganizationConnectionCommand > ( ) . Received ( 1 )
. CreateAsync ( Arg . Is ( AssertHelper . AssertPropertyEqual ( typedModel . ToData ( ) ) ) ) ;
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_RequiresOwnerPermissions ( SutProvider < OrganizationConnectionsController > sutProvider )
{
2022-06-23 13:50:10 +02:00
sutProvider . GetDependency < IOrganizationConnectionRepository > ( )
. GetByIdAsync ( Arg . Any < Guid > ( ) )
. Returns ( new OrganizationConnection ( ) ) ;
2022-05-10 23:12:09 +02:00
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = > sutProvider . Sut . UpdateConnection ( default , null ) ) ;
2022-07-14 21:58:48 +02:00
Assert . Contains ( "You do not have permission to update this connection." , exception . Message ) ;
2022-05-10 23:12:09 +02:00
}
[Theory]
2022-07-14 21:58:48 +02:00
[BitAutoData(OrganizationConnectionType.CloudBillingSync)]
public async Task UpdateConnection_BillingSync_OnlyOneConnectionOfEachType ( OrganizationConnectionType type ,
2022-05-10 23:12:09 +02:00
OrganizationConnection existing1 , OrganizationConnection existing2 , BillingSyncConfig config ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
2022-07-14 21:58:48 +02:00
existing1 . Type = existing2 . Type = type ;
existing1 . Config = JsonSerializer . Serialize ( config ) ;
var typedModel = RequestModelFromEntity < BillingSyncConfig > ( existing1 ) ;
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( typedModel . OrganizationId ) . Returns ( true ) ;
var orgConnectionRepository = sutProvider . GetDependency < IOrganizationConnectionRepository > ( ) ;
orgConnectionRepository . GetByIdAsync ( existing1 . Id ) . Returns ( existing1 ) ;
orgConnectionRepository . GetByIdAsync ( existing2 . Id ) . Returns ( existing2 ) ;
orgConnectionRepository . GetByOrganizationIdTypeAsync ( typedModel . OrganizationId , type ) . Returns ( new [ ] { existing1 , existing2 } ) ;
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = > sutProvider . Sut . UpdateConnection ( existing1 . Id , typedModel ) ) ;
Assert . Contains ( $"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization." , exception . Message ) ;
}
[Theory]
[BitAutoData(OrganizationConnectionType.Scim)]
public async Task UpdateConnection_Scim_OnlyOneConnectionOfEachType ( OrganizationConnectionType type ,
OrganizationConnection existing1 , OrganizationConnection existing2 , ScimConfig config ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
existing1 . Type = existing2 . Type = type ;
2022-05-10 23:12:09 +02:00
existing1 . Config = JsonSerializer . Serialize ( config ) ;
2022-07-14 21:58:48 +02:00
var typedModel = RequestModelFromEntity < ScimConfig > ( existing1 ) ;
2022-05-10 23:12:09 +02:00
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( typedModel . OrganizationId ) . Returns ( true ) ;
2022-06-23 13:50:10 +02:00
sutProvider . GetDependency < IOrganizationConnectionRepository > ( )
. GetByIdAsync ( existing1 . Id )
. Returns ( existing1 ) ;
2022-07-14 21:58:48 +02:00
sutProvider . GetDependency < ICurrentContext > ( ) . ManageScim ( typedModel . OrganizationId ) . Returns ( true ) ;
sutProvider . GetDependency < IOrganizationConnectionRepository > ( )
. GetByOrganizationIdTypeAsync ( typedModel . OrganizationId , type )
. Returns ( new [ ] { existing1 , existing2 } ) ;
2022-05-10 23:12:09 +02:00
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = > sutProvider . Sut . UpdateConnection ( existing1 . Id , typedModel ) ) ;
Assert . Contains ( $"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization." , exception . Message ) ;
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_Success ( OrganizationConnection existing , BillingSyncConfig config ,
OrganizationConnection updated ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
2022-06-23 13:50:10 +02:00
existing . SetConfig ( new BillingSyncConfig
{
CloudOrganizationId = config . CloudOrganizationId ,
} ) ;
2022-05-10 23:12:09 +02:00
updated . Config = JsonSerializer . Serialize ( config ) ;
updated . Id = existing . Id ;
2022-07-14 21:58:48 +02:00
updated . Type = OrganizationConnectionType . CloudBillingSync ;
var model = RequestModelFromEntity < BillingSyncConfig > ( updated ) ;
2022-05-10 23:12:09 +02:00
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( model . OrganizationId ) . Returns ( true ) ;
2022-07-14 21:58:48 +02:00
sutProvider . GetDependency < IOrganizationConnectionRepository > ( )
. GetByOrganizationIdTypeAsync ( model . OrganizationId , model . Type )
. Returns ( new [ ] { existing } ) ;
sutProvider . GetDependency < IUpdateOrganizationConnectionCommand > ( )
. UpdateAsync < BillingSyncConfig > ( default )
. ReturnsForAnyArgs ( updated ) ;
2022-06-23 13:50:10 +02:00
sutProvider . GetDependency < IOrganizationConnectionRepository > ( )
. GetByIdAsync ( existing . Id )
. Returns ( existing ) ;
2022-05-10 23:12:09 +02:00
var expected = new OrganizationConnectionResponseModel ( updated , typeof ( BillingSyncConfig ) ) ;
var result = await sutProvider . Sut . UpdateConnection ( existing . Id , model ) ;
AssertHelper . AssertPropertyEqual ( expected , result ) ;
await sutProvider . GetDependency < IUpdateOrganizationConnectionCommand > ( ) . Received ( 1 )
. UpdateAsync ( Arg . Is ( AssertHelper . AssertPropertyEqual ( model . ToData ( updated . Id ) ) ) ) ;
}
2022-06-23 13:50:10 +02:00
[Theory]
[BitAutoData]
public async Task UpdateConnection_DoesNotExist_ThrowsNotFound ( SutProvider < OrganizationConnectionsController > sutProvider )
{
await Assert . ThrowsAsync < NotFoundException > ( ( ) = > sutProvider . Sut . UpdateConnection ( Guid . NewGuid ( ) , null ) ) ;
}
2022-05-10 23:12:09 +02:00
[Theory]
[BitAutoData]
public async Task GetConnection_RequiresOwnerPermissions ( Guid connectionId , SutProvider < OrganizationConnectionsController > sutProvider )
{
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = >
sutProvider . Sut . GetConnection ( connectionId , OrganizationConnectionType . CloudBillingSync ) ) ;
2022-07-14 21:58:48 +02:00
Assert . Contains ( "You do not have permission to retrieve a connection of type" , exception . Message ) ;
2022-05-10 23:12:09 +02:00
}
[Theory]
[BitAutoData]
public async Task GetConnection_Success ( OrganizationConnection connection , BillingSyncConfig config ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
connection . Config = JsonSerializer . Serialize ( config ) ;
2022-07-14 21:58:48 +02:00
sutProvider . GetDependency < IGlobalSettings > ( ) . SelfHosted . Returns ( true ) ;
sutProvider . GetDependency < IOrganizationConnectionRepository > ( )
. GetByOrganizationIdTypeAsync ( connection . OrganizationId , connection . Type )
. Returns ( new [ ] { connection } ) ;
2022-05-10 23:12:09 +02:00
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( connection . OrganizationId ) . Returns ( true ) ;
var expected = new OrganizationConnectionResponseModel ( connection , typeof ( BillingSyncConfig ) ) ;
var actual = await sutProvider . Sut . GetConnection ( connection . OrganizationId , connection . Type ) ;
AssertHelper . AssertPropertyEqual ( expected , actual ) ;
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_NotFound ( Guid connectionId ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
await Assert . ThrowsAsync < NotFoundException > ( ( ) = > sutProvider . Sut . DeleteConnection ( connectionId ) ) ;
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_RequiresOwnerPermissions ( OrganizationConnection connection ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
sutProvider . GetDependency < IOrganizationConnectionRepository > ( ) . GetByIdAsync ( connection . Id ) . Returns ( connection ) ;
var exception = await Assert . ThrowsAsync < BadRequestException > ( ( ) = > sutProvider . Sut . DeleteConnection ( connection . Id ) ) ;
2022-07-14 21:58:48 +02:00
Assert . Contains ( "You do not have permission to remove this connection of type" , exception . Message ) ;
2022-05-10 23:12:09 +02:00
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_Success ( OrganizationConnection connection ,
SutProvider < OrganizationConnectionsController > sutProvider )
{
sutProvider . GetDependency < IOrganizationConnectionRepository > ( ) . GetByIdAsync ( connection . Id ) . Returns ( connection ) ;
sutProvider . GetDependency < ICurrentContext > ( ) . OrganizationOwner ( connection . OrganizationId ) . Returns ( true ) ;
await sutProvider . Sut . DeleteConnection ( connection . Id ) ;
await sutProvider . GetDependency < IDeleteOrganizationConnectionCommand > ( ) . DeleteAsync ( connection ) ;
}
2022-07-14 21:58:48 +02:00
private static OrganizationConnectionRequestModel < T > RequestModelFromEntity < T > ( OrganizationConnection entity )
where T : new ( )
2022-05-10 23:12:09 +02:00
{
return new ( new OrganizationConnectionRequestModel ( )
{
Type = entity . Type ,
OrganizationId = entity . OrganizationId ,
Enabled = entity . Enabled ,
Config = JsonDocument . Parse ( entity . Config ) ,
} ) ;
}
private static JsonDocument JsonDocumentFromObject < T > ( T obj ) = > JsonDocument . Parse ( JsonSerializer . Serialize ( obj ) ) ;
}
}