mirror of
https://github.com/bitwarden/server
synced 2026-01-09 12:03:21 +00:00
[PM-19145] refactor organization service.import async (#5800)
* initial lift and shift * extract function RemoveExistingExternalUsers * Extract function RemoveExistingUsers() * extract function OverwriteExisting() * create new model for sync data * extract add users to function, rename * rename OrganizatinUserInvite for command, implement command * implement command * refactor groups logic * fix imports * remove old tests, fix imports * fix namespace * fix CommandResult useage * tests wip * wip * wip * remove redundant code, remove looping db call, refactor tests * clean up * remove looping db call with bulk method * clean up * remove orgId param to use id already in request * change param * cleanup params * remove IReferenceEventService * fix test * fix tests * cr feedback * remove _timeProvider * add xmldoc, refactor to make InviteOrganizationUsersCommand vNext instead of default * switch back to command * re-add old ImportAsync impl * fix test * add feature flag * cleanup * clean up * fix tests * wip * wip * add api integration tests for users WIP * groups integration tests * cleanup * fix error from merging main * fix tests * cr feedback * fix test * fix test
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
using System.Net;
|
||||
using Bit.Api.AdminConsole.Public.Models.Request;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Import;
|
||||
|
||||
public class ImportOrganizationUsersAndGroupsCommandTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
private Organization _organization = null!;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
public ImportOrganizationUsersAndGroupsCommandTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_factory.SubstituteService((IFeatureService featureService)
|
||||
=> featureService.IsEnabled(FeatureFlagKeys.ImportAsyncRefactor)
|
||||
.Returns(true));
|
||||
_client = _factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Create the owner account
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
// Create the organization
|
||||
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023,
|
||||
ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
// Authorize with the organization api key
|
||||
await _loginHelper.LoginWithOrganizationApiKeyAsync(_organization.Id);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_Existing_Organization_User_Succeeds()
|
||||
{
|
||||
var (email, ou) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id,
|
||||
OrganizationUserType.User);
|
||||
|
||||
var externalId = Guid.NewGuid().ToString();
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [];
|
||||
request.Members = [
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = email,
|
||||
ExternalId = externalId,
|
||||
Deleted = false
|
||||
}
|
||||
];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var orgUser = await organizationUserRepository.GetByIdAsync(ou.Id);
|
||||
|
||||
Assert.NotNull(orgUser);
|
||||
Assert.Equal(ou.Id, orgUser.Id);
|
||||
Assert.Equal(email, orgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, orgUser.Type);
|
||||
Assert.Equal(externalId, orgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, orgUser.Status);
|
||||
Assert.Equal(_organization.Id, orgUser.OrganizationId);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_Organization_User_Succeeds()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
|
||||
var externalId = Guid.NewGuid().ToString();
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [];
|
||||
request.Members = [
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = email,
|
||||
ExternalId = externalId,
|
||||
Deleted = false
|
||||
}
|
||||
];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var orgUser = await organizationUserRepository.GetByOrganizationEmailAsync(_organization.Id, email);
|
||||
|
||||
Assert.NotNull(orgUser);
|
||||
Assert.Equal(email, orgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, orgUser.Type);
|
||||
Assert.Equal(externalId, orgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Invited, orgUser.Status);
|
||||
Assert.Equal(_organization.Id, orgUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_And_Existing_Organization_Users_Succeeds()
|
||||
{
|
||||
// Existing organization user
|
||||
var (existingEmail, ou) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id,
|
||||
OrganizationUserType.User);
|
||||
var existingExternalId = Guid.NewGuid().ToString();
|
||||
|
||||
// New organization user
|
||||
var newEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(newEmail);
|
||||
var newExternalId = Guid.NewGuid().ToString();
|
||||
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [];
|
||||
request.Members = [
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = existingEmail,
|
||||
ExternalId = existingExternalId,
|
||||
Deleted = false
|
||||
},
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = newEmail,
|
||||
ExternalId = newExternalId,
|
||||
Deleted = false
|
||||
}
|
||||
];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
// Existing user
|
||||
var existingOrgUser = await organizationUserRepository.GetByIdAsync(ou.Id);
|
||||
Assert.NotNull(existingOrgUser);
|
||||
Assert.Equal(existingEmail, existingOrgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, existingOrgUser.Type);
|
||||
Assert.Equal(existingExternalId, existingOrgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, existingOrgUser.Status);
|
||||
Assert.Equal(_organization.Id, existingOrgUser.OrganizationId);
|
||||
|
||||
// New User
|
||||
var newOrgUser = await organizationUserRepository.GetByOrganizationEmailAsync(_organization.Id, newEmail);
|
||||
Assert.NotNull(newOrgUser);
|
||||
Assert.Equal(newEmail, newOrgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, newOrgUser.Type);
|
||||
Assert.Equal(newExternalId, newOrgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Invited, newOrgUser.Status);
|
||||
Assert.Equal(_organization.Id, newOrgUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_Existing_Groups_Succeeds()
|
||||
{
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var group = await OrganizationTestHelpers.CreateGroup(_factory, _organization.Id);
|
||||
var request = new OrganizationImportRequestModel();
|
||||
var addedMember = new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = "test@test.com",
|
||||
ExternalId = "bwtest-externalId",
|
||||
Deleted = false
|
||||
};
|
||||
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = "new-name",
|
||||
ExternalId = "bwtest-externalId",
|
||||
MemberExternalIds = []
|
||||
}
|
||||
];
|
||||
request.Members = [addedMember];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var existingGroups = (await groupRepository.GetManyByOrganizationIdAsync(_organization.Id)).ToArray();
|
||||
|
||||
// Assert that we are actually updating the existing group, not adding a new one.
|
||||
Assert.Single(existingGroups);
|
||||
Assert.NotNull(existingGroups[0]);
|
||||
Assert.Equal(group.Id, existingGroups[0].Id);
|
||||
Assert.Equal("new-name", existingGroups[0].Name);
|
||||
Assert.Equal(group.ExternalId, existingGroups[0].ExternalId);
|
||||
|
||||
var addedOrgUser = await organizationUserRepository.GetByOrganizationEmailAsync(_organization.Id, addedMember.Email);
|
||||
Assert.NotNull(addedOrgUser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_Groups_Succeeds()
|
||||
{
|
||||
var group = new Group
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
ExternalId = new Guid().ToString(),
|
||||
Name = "bwtest1"
|
||||
};
|
||||
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = group.Name,
|
||||
ExternalId = group.ExternalId,
|
||||
MemberExternalIds = []
|
||||
}
|
||||
];
|
||||
request.Members = [];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var existingGroups = await groupRepository.GetManyByOrganizationIdAsync(_organization.Id);
|
||||
var existingGroup = existingGroups.Where(g => g.ExternalId == group.ExternalId).FirstOrDefault();
|
||||
|
||||
Assert.NotNull(existingGroup);
|
||||
Assert.Equal(existingGroup.Name, group.Name);
|
||||
Assert.Equal(existingGroup.ExternalId, group.ExternalId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_And_Existing_Groups_Succeeds()
|
||||
{
|
||||
var existingGroup = await OrganizationTestHelpers.CreateGroup(_factory, _organization.Id);
|
||||
|
||||
var newGroup = new Group
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
ExternalId = "test",
|
||||
Name = "bwtest1"
|
||||
};
|
||||
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = "new-name",
|
||||
ExternalId = existingGroup.ExternalId,
|
||||
MemberExternalIds = []
|
||||
},
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = newGroup.Name,
|
||||
ExternalId = newGroup.ExternalId,
|
||||
MemberExternalIds = []
|
||||
}
|
||||
];
|
||||
request.Members = [];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var groups = await groupRepository.GetManyByOrganizationIdAsync(_organization.Id);
|
||||
|
||||
var newGroupInDb = groups.Where(g => g.ExternalId == newGroup.ExternalId).FirstOrDefault();
|
||||
Assert.NotNull(newGroupInDb);
|
||||
Assert.Equal(newGroupInDb.Name, newGroup.Name);
|
||||
Assert.Equal(newGroupInDb.ExternalId, newGroup.ExternalId);
|
||||
|
||||
var existingGroupInDb = groups.Where(g => g.ExternalId == existingGroup.ExternalId).FirstOrDefault();
|
||||
Assert.NotNull(existingGroupInDb);
|
||||
Assert.Equal(existingGroup.Id, existingGroupInDb.Id);
|
||||
Assert.Equal("new-name", existingGroupInDb.Name);
|
||||
Assert.Equal(existingGroup.ExternalId, existingGroupInDb.ExternalId);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -78,6 +79,7 @@ public static class OrganizationTestHelpers
|
||||
Status = userStatusType,
|
||||
ExternalId = null,
|
||||
AccessSecretsManager = accessSecretsManager,
|
||||
Email = userEmail
|
||||
};
|
||||
|
||||
if (permissions != null)
|
||||
@@ -130,4 +132,20 @@ public static class OrganizationTestHelpers
|
||||
|
||||
await organizationDomainRepository.CreateAsync(verifiedDomain);
|
||||
}
|
||||
|
||||
public static async Task<Group> CreateGroup(ApiApplicationFactory factory, Guid organizationId)
|
||||
{
|
||||
|
||||
var groupRepository = factory.GetService<IGroupRepository>();
|
||||
var group = new Group
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Id = new Guid(),
|
||||
ExternalId = "bwtest-externalId",
|
||||
Name = "bwtest"
|
||||
};
|
||||
|
||||
await groupRepository.CreateAsync(group, new List<CollectionAccessSelection>());
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class InviteOrganizationUsersRequestTests
|
||||
public void Constructor_WhenPassedInvalidEmail_ThrowsException(string email, OrganizationUserType type, Permissions permissions, string externalId)
|
||||
{
|
||||
var exception = Assert.Throws<BadRequestException>(() =>
|
||||
new OrganizationUserInvite(email, [], [], type, permissions, externalId, false));
|
||||
new OrganizationUserInviteCommandModel(email, [], [], type, permissions, externalId, false));
|
||||
|
||||
Assert.Contains(InvalidEmailErrorMessage, exception.Message);
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class InviteOrganizationUsersRequestTests
|
||||
};
|
||||
|
||||
var exception = Assert.Throws<BadRequestException>(() =>
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: validEmail,
|
||||
assignedCollections: [invalidCollectionConfiguration],
|
||||
groups: [],
|
||||
@@ -51,7 +51,7 @@ public class InviteOrganizationUsersRequestTests
|
||||
const string validEmail = "test@email.com";
|
||||
var validCollectionConfiguration = new CollectionAccessSelection { Id = Guid.NewGuid(), Manage = true };
|
||||
|
||||
var invite = new OrganizationUserInvite(
|
||||
var invite = new OrganizationUserInviteCommandModel(
|
||||
email: validEmail,
|
||||
assignedCollections: [validCollectionConfiguration],
|
||||
groups: [],
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Fakes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Organization = Bit.Core.AdminConsole.Entities.Organization;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
public class ImportOrganizationUsersAndGroupsCommandTests
|
||||
{
|
||||
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
|
||||
|
||||
[Theory, PaidOrganizationCustomize, BitAutoData]
|
||||
public async Task OrgImportCallsInviteOrgUserCommand(
|
||||
SutProvider<ImportOrganizationUsersAndGroupsCommand> sutProvider,
|
||||
Organization org,
|
||||
List<OrganizationUserUserDetails> existingUsers,
|
||||
List<ImportedOrganizationUser> importedUsers,
|
||||
List<ImportedGroup> newGroups)
|
||||
{
|
||||
SetupOrganizationConfigForImport(sutProvider, org, existingUsers, importedUsers);
|
||||
|
||||
var orgUsers = new List<OrganizationUser>();
|
||||
|
||||
// fix mocked email format, mock OrganizationUsers.
|
||||
foreach (var u in importedUsers)
|
||||
{
|
||||
u.Email += "@bitwardentest.com";
|
||||
orgUsers.Add(new OrganizationUser { Email = u.Email, ExternalId = u.ExternalId });
|
||||
}
|
||||
|
||||
importedUsers.Add(new ImportedOrganizationUser
|
||||
{
|
||||
Email = existingUsers.First().Email,
|
||||
ExternalId = existingUsers.First().ExternalId
|
||||
});
|
||||
|
||||
|
||||
existingUsers.First().Type = OrganizationUserType.Owner;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
||||
|
||||
sutProvider.GetDependency<IPaymentService>().HasSecretsManagerStandalone(org).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyDetailsByOrganizationAsync(org.Id).Returns(existingUsers);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetOccupiedSeatCountByOrganizationIdAsync(org.Id).Returns(
|
||||
new OrganizationSeatCounts
|
||||
{
|
||||
Users = existingUsers.Count,
|
||||
Sponsored = 0
|
||||
});
|
||||
sutProvider.GetDependency<ICurrentContext>().ManageUsers(org.Id).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationService>().InviteUsersAsync(org.Id, Guid.Empty, EventSystemUser.PublicApi,
|
||||
Arg.Any<IEnumerable<(OrganizationUserInvite, string)>>())
|
||||
.Returns(orgUsers);
|
||||
|
||||
await sutProvider.Sut.ImportAsync(org.Id, newGroups, importedUsers, new List<string>(), false);
|
||||
|
||||
var expectedNewUsersCount = importedUsers.Count - 1;
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => !users.Any()));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
|
||||
// Send Invites
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).
|
||||
InviteUsersAsync(org.Id, Guid.Empty, EventSystemUser.PublicApi,
|
||||
Arg.Is<IEnumerable<(OrganizationUserInvite, string)>>(invites => invites.Count() == expectedNewUsersCount));
|
||||
|
||||
// Send events
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUserUserDetails, EventType, EventSystemUser, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize, BitAutoData]
|
||||
public async Task OrgImportCreateNewUsersAndMarryExistingUser(
|
||||
SutProvider<ImportOrganizationUsersAndGroupsCommand> sutProvider,
|
||||
Organization org,
|
||||
List<OrganizationUserUserDetails> existingUsers,
|
||||
List<ImportedOrganizationUser> importedUsers,
|
||||
List<ImportedGroup> newGroups)
|
||||
{
|
||||
SetupOrganizationConfigForImport(sutProvider, org, existingUsers, importedUsers);
|
||||
|
||||
var orgUsers = new List<OrganizationUser>();
|
||||
var reInvitedUser = existingUsers.First();
|
||||
// Existing user has no external ID. This will make the SUT call UpsertManyAsync
|
||||
reInvitedUser.ExternalId = "";
|
||||
|
||||
// Mock an existing org user for this "existing" user
|
||||
var reInvitedOrgUser = new OrganizationUser { Email = reInvitedUser.Email, Id = reInvitedUser.Id };
|
||||
|
||||
// fix email formatting, mock orgUsers to be returned
|
||||
foreach (var u in existingUsers)
|
||||
{
|
||||
u.Email += "@bitwardentest.com";
|
||||
orgUsers.Add(new OrganizationUser { Email = u.Email, ExternalId = u.ExternalId });
|
||||
}
|
||||
foreach (var u in importedUsers)
|
||||
{
|
||||
u.Email += "@bitwardentest.com";
|
||||
orgUsers.Add(new OrganizationUser { Email = u.Email, ExternalId = u.ExternalId });
|
||||
}
|
||||
|
||||
// add the existing user to be re-imported
|
||||
importedUsers.Add(new ImportedOrganizationUser
|
||||
{
|
||||
Email = reInvitedUser.Email,
|
||||
ExternalId = reInvitedUser.Email,
|
||||
});
|
||||
|
||||
var expectedNewUsersCount = importedUsers.Count - 1;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser>([reInvitedOrgUser]));
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyDetailsByOrganizationAsync(org.Id).Returns(existingUsers);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetOccupiedSeatCountByOrganizationIdAsync(org.Id).Returns(
|
||||
new OrganizationSeatCounts
|
||||
{
|
||||
Users = existingUsers.Count,
|
||||
Sponsored = 0
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationService>().InviteUsersAsync(org.Id, Guid.Empty, EventSystemUser.PublicApi,
|
||||
Arg.Any<IEnumerable<(OrganizationUserInvite, string)>>())
|
||||
.Returns(orgUsers);
|
||||
|
||||
await sutProvider.Sut.ImportAsync(org.Id, newGroups, importedUsers, new List<string>(), false);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default, default);
|
||||
|
||||
// Upserted existing user
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == 1 && users.First() == reInvitedOrgUser));
|
||||
|
||||
// Send Invites
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).
|
||||
InviteUsersAsync(org.Id, Guid.Empty, EventSystemUser.PublicApi,
|
||||
Arg.Is<IEnumerable<(OrganizationUserInvite, string)>>(invites => invites.Count() == expectedNewUsersCount));
|
||||
|
||||
// Send events
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUserUserDetails, EventType, EventSystemUser, DateTime?)>>());
|
||||
}
|
||||
|
||||
private void SetupOrganizationConfigForImport(
|
||||
SutProvider<ImportOrganizationUsersAndGroupsCommand> sutProvider,
|
||||
Organization org,
|
||||
List<OrganizationUserUserDetails> existingUsers,
|
||||
List<ImportedOrganizationUser> importedUsers)
|
||||
{
|
||||
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
|
||||
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
|
||||
sutProvider.Create();
|
||||
|
||||
org.UseDirectory = true;
|
||||
org.Seats = importedUsers.Count + existingUsers.Count + 1;
|
||||
}
|
||||
|
||||
// Must set real guids in order for dictionary of guids to not throw aggregate exceptions
|
||||
private void SetupOrgUserRepositoryCreateManyAsyncMock(IOrganizationUserRepository organizationUserRepository)
|
||||
{
|
||||
organizationUserRepository.CreateManyAsync(Arg.Any<IEnumerable<OrganizationUser>>()).Returns(
|
||||
info =>
|
||||
{
|
||||
var orgUsers = info.Arg<IEnumerable<OrganizationUser>>();
|
||||
foreach (var orgUser in orgUsers)
|
||||
{
|
||||
orgUser.Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
return Task.FromResult<ICollection<Guid>>(orgUsers.Select(u => u.Id).ToList());
|
||||
}
|
||||
);
|
||||
|
||||
organizationUserRepository.CreateAsync(Arg.Any<OrganizationUser>(), Arg.Any<IEnumerable<CollectionAccessSelection>>()).Returns(
|
||||
info =>
|
||||
{
|
||||
var orgUser = info.Arg<OrganizationUser>();
|
||||
orgUser.Id = Guid.NewGuid();
|
||||
return Task.FromResult<Guid>(orgUser.Id);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers;
|
||||
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
|
||||
@@ -54,7 +53,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -112,7 +111,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: orgUser.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -182,7 +181,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -257,7 +256,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -334,7 +333,7 @@ public class InviteOrganizationUserCommandTests
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites:
|
||||
[
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -411,7 +410,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -492,7 +491,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -566,7 +565,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -669,7 +668,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -768,7 +767,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites: [
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -863,7 +862,7 @@ public class InviteOrganizationUserCommandTests
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites:
|
||||
[
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
@@ -942,7 +941,7 @@ public class InviteOrganizationUserCommandTests
|
||||
var request = new InviteOrganizationUsersRequest(
|
||||
invites:
|
||||
[
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: user.Email,
|
||||
assignedCollections: [],
|
||||
groups: [],
|
||||
|
||||
@@ -14,7 +14,6 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
|
||||
@@ -36,13 +35,13 @@ public class InviteOrganizationUsersValidatorTests
|
||||
{
|
||||
Invites =
|
||||
[
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test@email.com",
|
||||
externalId: "test-external-id"),
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test2@email.com",
|
||||
externalId: "test-external-id2"),
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test3@email.com",
|
||||
externalId: "test-external-id3")
|
||||
],
|
||||
@@ -82,13 +81,13 @@ public class InviteOrganizationUsersValidatorTests
|
||||
{
|
||||
Invites =
|
||||
[
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test@email.com",
|
||||
externalId: "test-external-id"),
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test2@email.com",
|
||||
externalId: "test-external-id2"),
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test3@email.com",
|
||||
externalId: "test-external-id3")
|
||||
],
|
||||
@@ -126,13 +125,13 @@ public class InviteOrganizationUsersValidatorTests
|
||||
{
|
||||
Invites =
|
||||
[
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test@email.com",
|
||||
externalId: "test-external-id"),
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test2@email.com",
|
||||
externalId: "test-external-id2"),
|
||||
new OrganizationUserInvite(
|
||||
new OrganizationUserInviteCommandModel(
|
||||
email: "test3@email.com",
|
||||
externalId: "test-external-id3")
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user