From c11cef2bf206eaf767e8cc9ab6f1cc598becb69d Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 12 Jun 2025 16:19:39 -0400 Subject: [PATCH] add api integration tests for users WIP --- .../Controllers/OrganizationController.cs | 27 ++-- ...IImportOrganizationUserAndGroupsCommand.cs | 2 +- ...ImportOrganizationUsersAndGroupsCommand.cs | 4 +- ...tOrganizationUsersAndGroupsCommandTests.cs | 128 ++++++++++++++++-- .../Helpers/OrganizationTestHelpers.cs | 1 + 5 files changed, 136 insertions(+), 26 deletions(-) diff --git a/src/Api/AdminConsole/Public/Controllers/OrganizationController.cs b/src/Api/AdminConsole/Public/Controllers/OrganizationController.cs index ad3b6b60d4..2f231022fb 100644 --- a/src/Api/AdminConsole/Public/Controllers/OrganizationController.cs +++ b/src/Api/AdminConsole/Public/Controllers/OrganizationController.cs @@ -54,25 +54,26 @@ public class OrganizationController : Controller throw new BadRequestException("You cannot import this much data at once."); } - string r = ""; - - r = await _importOrganizationUsersAndGroupsCommand.ImportAsync( - _currentContext.OrganizationId.Value, - model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)), - model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()), - model.Members.Where(u => u.Deleted).Select(u => u.ExternalId), - model.OverwriteExisting.GetValueOrDefault()); - - if (_featureService.IsEnabled(FeatureFlagKeys.ImportAsyncRefactor)) { - r = "success"; + await _importOrganizationUsersAndGroupsCommand.ImportAsync( + _currentContext.OrganizationId.Value, + model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)), + model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()), + model.Members.Where(u => u.Deleted).Select(u => u.ExternalId), + model.OverwriteExisting.GetValueOrDefault()); } else { - r = "fail"; + await _organizationService.ImportAsync( + _currentContext.OrganizationId.Value, + model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)), + model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()), + model.Members.Where(u => u.Deleted).Select(u => u.ExternalId), + model.OverwriteExisting.GetValueOrDefault(), + Core.Enums.EventSystemUser.PublicApi); } - return new OkObjectResult(r); + return new OkResult(); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Import/IImportOrganizationUserAndGroupsCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Import/IImportOrganizationUserAndGroupsCommand.cs index 20debd081c..b74da0a2e8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Import/IImportOrganizationUserAndGroupsCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Import/IImportOrganizationUserAndGroupsCommand.cs @@ -7,7 +7,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interface public interface IImportOrganizationUsersAndGroupsCommand { - Task ImportAsync(Guid organizationId, + Task ImportAsync(Guid organizationId, IEnumerable groups, IEnumerable newUsers, IEnumerable removeUserExternalIds, diff --git a/src/Core/AdminConsole/OrganizationFeatures/Import/ImportOrganizationUsersAndGroupsCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Import/ImportOrganizationUsersAndGroupsCommand.cs index 0c466d145e..8f004e5c85 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Import/ImportOrganizationUsersAndGroupsCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Import/ImportOrganizationUsersAndGroupsCommand.cs @@ -66,7 +66,7 @@ public class ImportOrganizationUsersAndGroupsCommand : IImportOrganizationUsersA /// who are not included in the current import. /// Thrown if the organization does not exist. /// Thrown if the organization is not configured to use directory syncing. - public async Task ImportAsync(Guid organizationId, + public async Task ImportAsync(Guid organizationId, IEnumerable importedGroups, IEnumerable importedUsers, IEnumerable removeUserExternalIds, @@ -101,8 +101,6 @@ public class ImportOrganizationUsersAndGroupsCommand : IImportOrganizationUsersA await ImportGroups(organization, importedGroups, importUserData); await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.ou, e.e, _EventSystemUser, e.d))); - - return "success"; } /// diff --git a/test/Api.IntegrationTest/AdminConsole/Import/ImportOrganizationUsersAndGroupsCommandTests.cs b/test/Api.IntegrationTest/AdminConsole/Import/ImportOrganizationUsersAndGroupsCommandTests.cs index 167df3ff9b..bce61add20 100644 --- a/test/Api.IntegrationTest/AdminConsole/Import/ImportOrganizationUsersAndGroupsCommandTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Import/ImportOrganizationUsersAndGroupsCommandTests.cs @@ -1,11 +1,18 @@ -using Bit.Api.AdminConsole.Public.Models.Request; +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.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, IAsyncLifetime { private readonly HttpClient _client; @@ -17,8 +24,9 @@ public class ImportOrganizationUsersAndGroupsCommandTests : IClassFixture featureService.IsEnabled(FeatureFlagKeys.ImportAsyncRefactor) + .Returns(true)); _client = _factory.CreateClient(); _loginHelper = new LoginHelper(_factory, _client); } @@ -44,11 +52,12 @@ public class ImportOrganizationUsersAndGroupsCommandTests : IClassFixture(); + 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(); + 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(); + + // 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); + } } diff --git a/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs b/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs index f2bc9f4bac..6a92b12909 100644 --- a/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs +++ b/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs @@ -78,6 +78,7 @@ public static class OrganizationTestHelpers Status = userStatusType, ExternalId = null, AccessSecretsManager = accessSecretsManager, + Email = userEmail }; if (permissions != null)