Browse Source
* Add GroupsRecipe to manage group creation and user relationships in organizations
* Add CollectionsRecipe to manage collection creation and user relationships in organizations
* Refactor OrganizationUsersControllerPerformanceTests to enhance performance testing and add new test cases
* Add OrganizationDomainRecipe to add verified domains for organizations
* Add more tests to OrganizationUsersControllerPerformanceTests and enhance seeding logic for organizations
- Updated performance tests to use dynamic domain generation for organization users.
- Refactored seeding methods in OrganizationWithUsersRecipe to accept user status and type.
- Modified AddToOrganization methods in CollectionsRecipe and GroupsRecipe to return created IDs.
- Adjusted DbSeederUtility to align with new seeding method signatures.
* Enhance OrganizationSeeder with additional configuration options and update seat calculation in OrganizationWithUsersRecipe to ensure a minimum of 1000 seats.
* Add performance tests for Groups, Organizations, Organization Users, and Provider Organizations controllers
- Introduced `GroupsControllerPerformanceTests` to validate the performance of the PutGroupAsync method.
- Added `OrganizationsControllerPerformanceTests` with multiple tests including DeleteOrganizationAsync, DeleteOrganizationWithTokenAsync, PostStorageAsync, and CreateWithoutPaymentAsync.
- Enhanced `OrganizationUsersControllerPerformanceTests` with DeleteSingleUserAccountAsync and InviteUsersAsync methods to test user account deletion and bulk invitations.
- Created `ProviderOrganizationsControllerPerformanceTests` to assess the performance of deleting provider organizations.
These tests ensure the reliability and efficiency of the respective controller actions under various scenarios.
* Refactor GroupsControllerPerformanceTests to use parameterized tests
- Renamed `GroupsControllerPerformanceTest` to `GroupsControllerPerformanceTests` for consistency.
- Updated `PutGroupAsync` method to use `[Theory]` with `InlineData` for dynamic user and collection counts.
- Adjusted organization user and collection seeding logic to utilize the new parameters.
- Enhanced logging to provide clearer performance metrics during tests.
* Update domain generation in GroupsControllerPerformanceTests for improved test consistency
* Remove ProviderOrganizationsControllerPerformanceTests
* Refactor performance tests for Groups, Organizations, and Organization Users controllers
- Updated method names for clarity and consistency, e.g., `PutGroupAsync` to `UpdateGroup_WithUsersAndCollections`.
- Enhanced test documentation with XML comments to describe the purpose of each test.
- Improved domain generation logic for consistency across tests.
- Adjusted logging to provide detailed performance metrics during test execution.
- Renamed several test methods to better reflect their functionality.
* Refactor performance tests in Organizations and Organization Users controllers
- Updated tests to use parameterized `[Theory]` attributes with `InlineData` for dynamic user, collection, and group counts.
- Enhanced logging to include detailed metrics such as user and collection counts during test execution.
- Marked several tests as skipped for performance considerations.
- Removed unused code and improved organization of test methods for clarity.
* Add bulk reinvite users performance test to OrganizationUsersControllerPerformanceTests
- Implemented a new performance test for the POST /organizations/{orgId}/users/reinvite endpoint.
- Utilized parameterized testing with `[Theory]` and `InlineData` to evaluate performance with varying user counts.
- Enhanced logging to capture request duration and response status for better performance insights.
- Updated OrganizationSeeder to conditionally set email based on user status during seeding.
* Refactor domain generation in performance tests to use OrganizationTestHelpers
- Updated domain generation logic in GroupsControllerPerformanceTests, OrganizationsControllerPerformanceTests, and OrganizationUsersControllerPerformanceTests to utilize the new GenerateRandomDomain method from OrganizationTestHelpers.
- This change enhances consistency and readability across the tests by centralizing domain generation logic.
* Update CollectionsRecipe to have better readability
* Update GroupsRecipe to have better readability
* Refactor authentication in performance tests to use centralized helper method. This change reduces code duplication across Groups, Organizations, and OrganizationUsers controller tests by implementing the `AuthenticateClientAsync` method in a new `PerformanceTestHelpers` class.
* Refactor OrganizationUsersControllerPerformanceTests to filter organization users by OrganizationId.
* Refactor CreateOrganizationUser method to improve handling of user status and key assignment based on invitation and confirmation states.
* Add XML documentation for CreateOrganizationUser method to clarify user status handling
pull/6694/head
11 changed files with 1124 additions and 28 deletions
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
using System.Net; |
||||
using System.Text; |
||||
using System.Text.Json; |
||||
using Bit.Api.AdminConsole.Models.Request; |
||||
using Bit.Api.IntegrationTest.Factories; |
||||
using Bit.Api.IntegrationTest.Helpers; |
||||
using Bit.Api.Models.Request; |
||||
using Bit.Seeder.Recipes; |
||||
using Xunit; |
||||
using Xunit.Abstractions; |
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; |
||||
|
||||
public class GroupsControllerPerformanceTests(ITestOutputHelper testOutputHelper) |
||||
{ |
||||
/// <summary> |
||||
/// Tests PUT /organizations/{orgId}/groups/{id} |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10, 5)] |
||||
//[InlineData(100, 10)] |
||||
//[InlineData(1000, 20)] |
||||
public async Task UpdateGroup_WithUsersAndCollections(int userCount, int collectionCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
var collectionIds = collectionsSeeder.AddToOrganization(orgId, collectionCount, orgUserIds, 0); |
||||
var groupIds = groupsSeeder.AddToOrganization(orgId, 1, orgUserIds, 0); |
||||
|
||||
var groupId = groupIds.First(); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var updateRequest = new GroupRequestModel |
||||
{ |
||||
Name = "Updated Group Name", |
||||
Collections = collectionIds.Select(c => new SelectionReadOnlyRequestModel { Id = c, ReadOnly = false, HidePasswords = false, Manage = false }), |
||||
Users = orgUserIds |
||||
}; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(updateRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PutAsync($"/organizations/{orgId}/groups/{groupId}", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"PUT /organizations/{{orgId}}/groups/{{id}} - Users: {orgUserIds.Count}; Collections: {collectionIds.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
} |
||||
@ -1,39 +1,593 @@
@@ -1,39 +1,593 @@
|
||||
using System.Net; |
||||
using System.Net.Http.Headers; |
||||
using System.Text; |
||||
using System.Text.Json; |
||||
using Bit.Api.AdminConsole.Models.Request.Organizations; |
||||
using Bit.Api.IntegrationTest.Factories; |
||||
using Bit.Api.IntegrationTest.Helpers; |
||||
using Bit.Api.Models.Request; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Models.Data; |
||||
using Bit.Seeder.Recipes; |
||||
using Xunit; |
||||
using Xunit.Abstractions; |
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; |
||||
|
||||
public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOutputHelper) |
||||
public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testOutputHelper) |
||||
{ |
||||
/// <summary> |
||||
/// Tests GET /organizations/{orgId}/users?includeCollections=true |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(100)] |
||||
[InlineData(60000)] |
||||
public async Task GetAsync(int seats) |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task GetAllUsers_WithCollections(int seats) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var seeder = new OrganizationWithUsersRecipe(db); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var orgId = seeder.Seed("Org", seats, "large.test"); |
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
|
||||
var tokens = await factory.LoginAsync("admin@large.test", "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs="); |
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: seats); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
collectionsSeeder.AddToOrganization(orgId, 10, orgUserIds); |
||||
groupsSeeder.AddToOrganization(orgId, 5, orgUserIds); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.GetAsync($"/organizations/{orgId}/users?includeCollections=true"); |
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
|
||||
var result = await response.Content.ReadAsStringAsync(); |
||||
Assert.NotEmpty(result); |
||||
stopwatch.Stop(); |
||||
testOutputHelper.WriteLine($"GET /users - Seats: {seats}; Request duration: {stopwatch.ElapsedMilliseconds} ms"); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests GET /organizations/{orgId}/users/mini-details |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task GetAllUsers_MiniDetails(int seats) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: seats); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
collectionsSeeder.AddToOrganization(orgId, 10, orgUserIds); |
||||
groupsSeeder.AddToOrganization(orgId, 5, orgUserIds); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.GetAsync($"/organizations/{orgId}/users/mini-details"); |
||||
|
||||
stopwatch.Stop(); |
||||
testOutputHelper.WriteLine($"Seed: {seats}; Request duration: {stopwatch.ElapsedMilliseconds} ms"); |
||||
|
||||
testOutputHelper.WriteLine($"GET /users/mini-details - Seats: {seats}; Request duration: {stopwatch.ElapsedMilliseconds} ms"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests GET /organizations/{orgId}/users/{id}?includeGroups=true |
||||
/// </summary> |
||||
[Fact(Skip = "Performance test")] |
||||
public async Task GetSingleUser_WithGroups() |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); |
||||
|
||||
var orgUserId = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).FirstOrDefault(); |
||||
groupsSeeder.AddToOrganization(orgId, 2, [orgUserId]); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.GetAsync($"/organizations/{orgId}/users/{orgUserId}?includeGroups=true"); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"GET /users/{{id}} - Request duration: {stopwatch.ElapsedMilliseconds} ms"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests GET /organizations/{orgId}/users/{id}/reset-password-details |
||||
/// </summary> |
||||
[Fact(Skip = "Performance test")] |
||||
public async Task GetResetPasswordDetails_ForSingleUser() |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); |
||||
|
||||
var orgUserId = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).FirstOrDefault(); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.GetAsync($"/organizations/{orgId}/users/{orgUserId}/reset-password-details"); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"GET /users/{{id}}/reset-password-details - Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/{orgId}/users/confirm |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkConfirmUsers(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed( |
||||
name: "Org", |
||||
domain: domain, |
||||
users: userCount, |
||||
usersStatus: OrganizationUserStatusType.Accepted); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var acceptedUserIds = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Status == OrganizationUserStatusType.Accepted) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var confirmRequest = new OrganizationUserBulkConfirmRequestModel |
||||
{ |
||||
Keys = acceptedUserIds.Select(id => new OrganizationUserBulkConfirmRequestModelEntry { Id = id, Key = "test-key-" + id }), |
||||
DefaultUserCollectionName = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=" |
||||
}; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(confirmRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PostAsync($"/organizations/{orgId}/users/confirm", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /users/confirm - Users: {acceptedUserIds.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/{orgId}/users/remove |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkRemoveUsers(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var usersToRemove = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var removeRequest = new OrganizationUserBulkRequestModel { Ids = usersToRemove }; |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(removeRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var response = await client.PostAsync($"/organizations/{orgId}/users/remove", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /users/remove - Users: {usersToRemove.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests PUT /organizations/{orgId}/users/revoke |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkRevokeUsers(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed( |
||||
name: "Org", |
||||
domain: domain, |
||||
users: userCount, |
||||
usersStatus: OrganizationUserStatusType.Confirmed); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var usersToRevoke = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var revokeRequest = new OrganizationUserBulkRequestModel { Ids = usersToRevoke }; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(revokeRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PutAsync($"/organizations/{orgId}/users/revoke", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"PUT /users/revoke - Users: {usersToRevoke.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests PUT /organizations/{orgId}/users/restore |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkRestoreUsers(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed( |
||||
name: "Org", |
||||
domain: domain, |
||||
users: userCount, |
||||
usersStatus: OrganizationUserStatusType.Revoked); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var usersToRestore = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var restoreRequest = new OrganizationUserBulkRequestModel { Ids = usersToRestore }; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(restoreRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PutAsync($"/organizations/{orgId}/users/restore", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"PUT /users/restore - Users: {usersToRestore.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/{orgId}/users/delete-account |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkDeleteAccounts(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var domainSeeder = new OrganizationDomainRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
|
||||
var orgId = orgSeeder.Seed( |
||||
name: "Org", |
||||
domain: domain, |
||||
users: userCount, |
||||
usersStatus: OrganizationUserStatusType.Confirmed); |
||||
|
||||
domainSeeder.AddVerifiedDomainToOrganization(orgId, domain); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var usersToDelete = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var deleteRequest = new OrganizationUserBulkRequestModel { Ids = usersToDelete }; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(deleteRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PostAsync($"/organizations/{orgId}/users/delete-account", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /users/delete-account - Users: {usersToDelete.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests PUT /organizations/{orgId}/users/{id} |
||||
/// </summary> |
||||
[Fact(Skip = "Performance test")] |
||||
public async Task UpdateSingleUser_WithCollectionsAndGroups() |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
var collectionIds = collectionsSeeder.AddToOrganization(orgId, 3, orgUserIds, 0); |
||||
var groupIds = groupsSeeder.AddToOrganization(orgId, 2, orgUserIds, 0); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var userToUpdate = db.OrganizationUsers |
||||
.FirstOrDefault(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User); |
||||
|
||||
var updateRequest = new OrganizationUserUpdateRequestModel |
||||
{ |
||||
Type = OrganizationUserType.Custom, |
||||
Collections = collectionIds.Select(c => new SelectionReadOnlyRequestModel { Id = c, ReadOnly = false, HidePasswords = false, Manage = false }), |
||||
Groups = groupIds, |
||||
AccessSecretsManager = false, |
||||
Permissions = new Permissions { AccessEventLogs = true } |
||||
}; |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PutAsync($"/organizations/{orgId}/users/{userToUpdate.Id}", |
||||
new StringContent(JsonSerializer.Serialize(updateRequest), Encoding.UTF8, "application/json")); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"PUT /users/{{id}} - Collections: {collectionIds.Count}; Groups: {groupIds.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests PUT /organizations/{orgId}/users/enable-secrets-manager |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkEnableSecretsManager(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var usersToEnable = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var enableRequest = new OrganizationUserBulkRequestModel { Ids = usersToEnable }; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(enableRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PutAsync($"/organizations/{orgId}/users/enable-secrets-manager", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"PUT /users/enable-secrets-manager - Users: {usersToEnable.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests DELETE /organizations/{orgId}/users/{id}/delete-account |
||||
/// </summary> |
||||
[Fact(Skip = "Performance test")] |
||||
public async Task DeleteSingleUserAccount_FromVerifiedDomain() |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var domainSeeder = new OrganizationDomainRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed( |
||||
name: "Org", |
||||
domain: domain, |
||||
users: 2, |
||||
usersStatus: OrganizationUserStatusType.Confirmed); |
||||
|
||||
domainSeeder.AddVerifiedDomainToOrganization(orgId, domain); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var userToDelete = db.OrganizationUsers |
||||
.FirstOrDefault(ou => ou.OrganizationId == orgId && ou.Type == OrganizationUserType.User); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.DeleteAsync($"/organizations/{orgId}/users/{userToDelete.Id}/delete-account"); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"DELETE /users/{{id}}/delete-account - Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/{orgId}/users/invite |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(1)] |
||||
//[InlineData(5)] |
||||
//[InlineData(20)] |
||||
public async Task InviteUsers(int emailCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
var collectionIds = collectionsSeeder.AddToOrganization(orgId, 2, orgUserIds, 0); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var emails = Enumerable.Range(0, emailCount).Select(i => $"{i:D4}@{domain}").ToArray(); |
||||
var inviteRequest = new OrganizationUserInviteRequestModel |
||||
{ |
||||
Emails = emails, |
||||
Type = OrganizationUserType.User, |
||||
AccessSecretsManager = false, |
||||
Collections = Array.Empty<SelectionReadOnlyRequestModel>(), |
||||
Groups = Array.Empty<Guid>(), |
||||
Permissions = null |
||||
}; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(inviteRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PostAsync($"/organizations/{orgId}/users/invite", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /users/invite - Emails: {emails.Length}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/{orgId}/users/reinvite |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10)] |
||||
//[InlineData(100)] |
||||
//[InlineData(1000)] |
||||
public async Task BulkReinviteUsers(int userCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed( |
||||
name: "Org", |
||||
domain: domain, |
||||
users: userCount, |
||||
usersStatus: OrganizationUserStatusType.Invited); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var usersToReinvite = db.OrganizationUsers |
||||
.Where(ou => ou.OrganizationId == orgId && ou.Status == OrganizationUserStatusType.Invited) |
||||
.Select(ou => ou.Id) |
||||
.ToList(); |
||||
|
||||
var reinviteRequest = new OrganizationUserBulkRequestModel { Ids = usersToReinvite }; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(reinviteRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PostAsync($"/organizations/{orgId}/users/reinvite", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /users/reinvite - Users: {usersToReinvite.Count}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.True(response.IsSuccessStatusCode); |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
using System.Net; |
||||
using System.Text; |
||||
using System.Text.Json; |
||||
using Bit.Api.AdminConsole.Models.Request.Organizations; |
||||
using Bit.Api.Auth.Models.Request.Accounts; |
||||
using Bit.Api.IntegrationTest.Factories; |
||||
using Bit.Api.IntegrationTest.Helpers; |
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables; |
||||
using Bit.Core.Billing.Enums; |
||||
using Bit.Core.Tokens; |
||||
using Bit.Seeder.Recipes; |
||||
using Xunit; |
||||
using Xunit.Abstractions; |
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; |
||||
|
||||
public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutputHelper) |
||||
{ |
||||
/// <summary> |
||||
/// Tests DELETE /organizations/{id} with password verification |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10, 5, 3)] |
||||
//[InlineData(100, 20, 10)] |
||||
//[InlineData(1000, 50, 25)] |
||||
public async Task DeleteOrganization_WithPasswordVerification(int userCount, int collectionCount, int groupCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
collectionsSeeder.AddToOrganization(orgId, collectionCount, orgUserIds, 0); |
||||
groupsSeeder.AddToOrganization(orgId, groupCount, orgUserIds, 0); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var deleteRequest = new SecretVerificationRequestModel |
||||
{ |
||||
MasterPasswordHash = "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs=" |
||||
}; |
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Delete, $"/organizations/{orgId}") |
||||
{ |
||||
Content = new StringContent(JsonSerializer.Serialize(deleteRequest), Encoding.UTF8, "application/json") |
||||
}; |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
|
||||
var response = await client.SendAsync(request); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"DELETE /organizations/{{id}} - Users: {userCount}; Collections: {collectionCount}; Groups: {groupCount}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/{id}/delete-recover-token with token verification |
||||
/// </summary> |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(10, 5, 3)] |
||||
//[InlineData(100, 20, 10)] |
||||
//[InlineData(1000, 50, 25)] |
||||
public async Task DeleteOrganization_WithTokenVerification(int userCount, int collectionCount, int groupCount) |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var orgSeeder = new OrganizationWithUsersRecipe(db); |
||||
var collectionsSeeder = new CollectionsRecipe(db); |
||||
var groupsSeeder = new GroupsRecipe(db); |
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain(); |
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); |
||||
|
||||
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); |
||||
collectionsSeeder.AddToOrganization(orgId, collectionCount, orgUserIds, 0); |
||||
groupsSeeder.AddToOrganization(orgId, groupCount, orgUserIds, 0); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); |
||||
|
||||
var organization = db.Organizations.FirstOrDefault(o => o.Id == orgId); |
||||
Assert.NotNull(organization); |
||||
|
||||
var tokenFactory = factory.GetService<IDataProtectorTokenFactory<OrgDeleteTokenable>>(); |
||||
var tokenable = new OrgDeleteTokenable(organization, 24); |
||||
var token = tokenFactory.Protect(tokenable); |
||||
|
||||
var deleteRequest = new OrganizationVerifyDeleteRecoverRequestModel |
||||
{ |
||||
Token = token |
||||
}; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(deleteRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PostAsync($"/organizations/{orgId}/delete-recover-token", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /organizations/{{id}}/delete-recover-token - Users: {userCount}; Collections: {collectionCount}; Groups: {groupCount}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Tests POST /organizations/create-without-payment |
||||
/// </summary> |
||||
[Fact(Skip = "Performance test")] |
||||
public async Task CreateOrganization_WithoutPayment() |
||||
{ |
||||
await using var factory = new SqlServerApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var email = $"user@{OrganizationTestHelpers.GenerateRandomDomain()}"; |
||||
var masterPasswordHash = "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs="; |
||||
|
||||
await factory.LoginWithNewAccount(email, masterPasswordHash); |
||||
|
||||
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, email, masterPasswordHash); |
||||
|
||||
var createRequest = new OrganizationNoPaymentCreateRequest |
||||
{ |
||||
Name = "Test Organization", |
||||
BusinessName = "Test Business Name", |
||||
BillingEmail = email, |
||||
PlanType = PlanType.EnterpriseAnnually, |
||||
Key = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=", |
||||
AdditionalSeats = 1, |
||||
AdditionalStorageGb = 1, |
||||
UseSecretsManager = true, |
||||
AdditionalSmSeats = 1, |
||||
AdditionalServiceAccounts = 2, |
||||
MaxAutoscaleSeats = 100, |
||||
PremiumAccessAddon = false, |
||||
CollectionName = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=" |
||||
}; |
||||
|
||||
var requestContent = new StringContent(JsonSerializer.Serialize(createRequest), Encoding.UTF8, "application/json"); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.PostAsync("/organizations/create-without-payment", requestContent); |
||||
|
||||
stopwatch.Stop(); |
||||
|
||||
testOutputHelper.WriteLine($"POST /organizations/create-without-payment - AdditionalSeats: {createRequest.AdditionalSeats}; AdditionalStorageGb: {createRequest.AdditionalStorageGb}; AdditionalSmSeats: {createRequest.AdditionalSmSeats}; AdditionalServiceAccounts: {createRequest.AdditionalServiceAccounts}; MaxAutoscaleSeats: {createRequest.MaxAutoscaleSeats}; Request duration: {stopwatch.ElapsedMilliseconds} ms; Status: {response.StatusCode}"); |
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
using System.Net.Http.Headers; |
||||
using Bit.Api.IntegrationTest.Factories; |
||||
|
||||
namespace Bit.Api.IntegrationTest.Helpers; |
||||
|
||||
/// <summary> |
||||
/// Helper methods for performance tests to reduce code duplication. |
||||
/// </summary> |
||||
public static class PerformanceTestHelpers |
||||
{ |
||||
/// <summary> |
||||
/// Standard password hash used across performance tests. |
||||
/// </summary> |
||||
public const string StandardPasswordHash = "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs="; |
||||
|
||||
/// <summary> |
||||
/// Authenticates an HttpClient with a bearer token for the specified user. |
||||
/// </summary> |
||||
/// <param name="factory">The application factory to use for login.</param> |
||||
/// <param name="client">The HttpClient to authenticate.</param> |
||||
/// <param name="email">The user's email address.</param> |
||||
/// <param name="masterPasswordHash">The user's master password hash. Defaults to StandardPasswordHash.</param> |
||||
public static async Task AuthenticateClientAsync( |
||||
SqlServerApiApplicationFactory factory, |
||||
HttpClient client, |
||||
string email, |
||||
string? masterPasswordHash = null) |
||||
{ |
||||
var tokens = await factory.LoginAsync(email, masterPasswordHash ?? StandardPasswordHash); |
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); |
||||
} |
||||
} |
||||
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Utilities; |
||||
using Bit.Infrastructure.EntityFramework.Repositories; |
||||
using LinqToDB.EntityFrameworkCore; |
||||
|
||||
namespace Bit.Seeder.Recipes; |
||||
|
||||
public class CollectionsRecipe(DatabaseContext db) |
||||
{ |
||||
/// <summary> |
||||
/// Adds collections to an organization and creates relationships between users and collections. |
||||
/// </summary> |
||||
/// <param name="organizationId">The ID of the organization to add collections to.</param> |
||||
/// <param name="collections">The number of collections to add.</param> |
||||
/// <param name="organizationUserIds">The IDs of the users to create relationships with.</param> |
||||
/// <param name="maxUsersWithRelationships">The maximum number of users to create relationships with.</param> |
||||
public List<Guid> AddToOrganization(Guid organizationId, int collections, List<Guid> organizationUserIds, int maxUsersWithRelationships = 1000) |
||||
{ |
||||
var collectionList = CreateAndSaveCollections(organizationId, collections); |
||||
|
||||
if (collectionList.Any()) |
||||
{ |
||||
CreateAndSaveCollectionUserRelationships(collectionList, organizationUserIds, maxUsersWithRelationships); |
||||
} |
||||
|
||||
return collectionList.Select(c => c.Id).ToList(); |
||||
} |
||||
|
||||
private List<Core.Entities.Collection> CreateAndSaveCollections(Guid organizationId, int count) |
||||
{ |
||||
var collectionList = new List<Core.Entities.Collection>(); |
||||
|
||||
for (var i = 0; i < count; i++) |
||||
{ |
||||
collectionList.Add(new Core.Entities.Collection |
||||
{ |
||||
Id = CoreHelpers.GenerateComb(), |
||||
OrganizationId = organizationId, |
||||
Name = $"Collection {i + 1}", |
||||
Type = CollectionType.SharedCollection, |
||||
CreationDate = DateTime.UtcNow, |
||||
RevisionDate = DateTime.UtcNow |
||||
}); |
||||
} |
||||
|
||||
if (collectionList.Any()) |
||||
{ |
||||
db.BulkCopy(collectionList); |
||||
} |
||||
|
||||
return collectionList; |
||||
} |
||||
|
||||
private void CreateAndSaveCollectionUserRelationships( |
||||
List<Core.Entities.Collection> collections, |
||||
List<Guid> organizationUserIds, |
||||
int maxUsersWithRelationships) |
||||
{ |
||||
if (!organizationUserIds.Any() || maxUsersWithRelationships <= 0) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
var collectionUsers = BuildCollectionUserRelationships(collections, organizationUserIds, maxUsersWithRelationships); |
||||
|
||||
if (collectionUsers.Any()) |
||||
{ |
||||
db.BulkCopy(collectionUsers); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Creates user-to-collection relationships with varied assignment patterns for realistic test data. |
||||
/// Each user gets 1-3 collections based on a rotating pattern. |
||||
/// </summary> |
||||
private List<Core.Entities.CollectionUser> BuildCollectionUserRelationships( |
||||
List<Core.Entities.Collection> collections, |
||||
List<Guid> organizationUserIds, |
||||
int maxUsersWithRelationships) |
||||
{ |
||||
var maxRelationships = Math.Min(organizationUserIds.Count, maxUsersWithRelationships); |
||||
var collectionUsers = new List<Core.Entities.CollectionUser>(); |
||||
|
||||
for (var i = 0; i < maxRelationships; i++) |
||||
{ |
||||
var orgUserId = organizationUserIds[i]; |
||||
var userCollectionAssignments = CreateCollectionAssignmentsForUser(collections, orgUserId, i); |
||||
collectionUsers.AddRange(userCollectionAssignments); |
||||
} |
||||
|
||||
return collectionUsers; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Assigns collections to a user with varying permissions. |
||||
/// Pattern: 1-3 collections per user (cycles: 1, 2, 3, 1, 2, 3...). |
||||
/// First collection has Manage rights, subsequent ones are ReadOnly. |
||||
/// </summary> |
||||
private List<Core.Entities.CollectionUser> CreateCollectionAssignmentsForUser( |
||||
List<Core.Entities.Collection> collections, |
||||
Guid organizationUserId, |
||||
int userIndex) |
||||
{ |
||||
var assignments = new List<Core.Entities.CollectionUser>(); |
||||
var userCollectionCount = (userIndex % 3) + 1; // Cycles through 1, 2, or 3 collections |
||||
|
||||
for (var j = 0; j < userCollectionCount; j++) |
||||
{ |
||||
var collectionIndex = (userIndex + j) % collections.Count; // Distribute across available collections |
||||
assignments.Add(new Core.Entities.CollectionUser |
||||
{ |
||||
CollectionId = collections[collectionIndex].Id, |
||||
OrganizationUserId = organizationUserId, |
||||
ReadOnly = j > 0, // First assignment gets write access |
||||
HidePasswords = false, |
||||
Manage = j == 0 // First assignment gets manage permissions |
||||
}); |
||||
} |
||||
|
||||
return assignments; |
||||
} |
||||
} |
||||
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
using Bit.Core.Utilities; |
||||
using Bit.Infrastructure.EntityFramework.Repositories; |
||||
using LinqToDB.EntityFrameworkCore; |
||||
|
||||
namespace Bit.Seeder.Recipes; |
||||
|
||||
public class GroupsRecipe(DatabaseContext db) |
||||
{ |
||||
/// <summary> |
||||
/// Adds groups to an organization and creates relationships between users and groups. |
||||
/// </summary> |
||||
/// <param name="organizationId">The ID of the organization to add groups to.</param> |
||||
/// <param name="groups">The number of groups to add.</param> |
||||
/// <param name="organizationUserIds">The IDs of the users to create relationships with.</param> |
||||
/// <param name="maxUsersWithRelationships">The maximum number of users to create relationships with.</param> |
||||
public List<Guid> AddToOrganization(Guid organizationId, int groups, List<Guid> organizationUserIds, int maxUsersWithRelationships = 1000) |
||||
{ |
||||
var groupList = CreateAndSaveGroups(organizationId, groups); |
||||
|
||||
if (groupList.Any()) |
||||
{ |
||||
CreateAndSaveGroupUserRelationships(groupList, organizationUserIds, maxUsersWithRelationships); |
||||
} |
||||
|
||||
return groupList.Select(g => g.Id).ToList(); |
||||
} |
||||
|
||||
private List<Core.AdminConsole.Entities.Group> CreateAndSaveGroups(Guid organizationId, int count) |
||||
{ |
||||
var groupList = new List<Core.AdminConsole.Entities.Group>(); |
||||
|
||||
for (var i = 0; i < count; i++) |
||||
{ |
||||
groupList.Add(new Core.AdminConsole.Entities.Group |
||||
{ |
||||
Id = CoreHelpers.GenerateComb(), |
||||
OrganizationId = organizationId, |
||||
Name = $"Group {i + 1}" |
||||
}); |
||||
} |
||||
|
||||
if (groupList.Any()) |
||||
{ |
||||
db.BulkCopy(groupList); |
||||
} |
||||
|
||||
return groupList; |
||||
} |
||||
|
||||
private void CreateAndSaveGroupUserRelationships( |
||||
List<Core.AdminConsole.Entities.Group> groups, |
||||
List<Guid> organizationUserIds, |
||||
int maxUsersWithRelationships) |
||||
{ |
||||
if (!organizationUserIds.Any() || maxUsersWithRelationships <= 0) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
var groupUsers = BuildGroupUserRelationships(groups, organizationUserIds, maxUsersWithRelationships); |
||||
|
||||
if (groupUsers.Any()) |
||||
{ |
||||
db.BulkCopy(groupUsers); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Creates user-to-group relationships with distributed assignment patterns for realistic test data. |
||||
/// Each user is assigned to one group, distributed evenly across available groups. |
||||
/// </summary> |
||||
private List<Core.AdminConsole.Entities.GroupUser> BuildGroupUserRelationships( |
||||
List<Core.AdminConsole.Entities.Group> groups, |
||||
List<Guid> organizationUserIds, |
||||
int maxUsersWithRelationships) |
||||
{ |
||||
var maxRelationships = Math.Min(organizationUserIds.Count, maxUsersWithRelationships); |
||||
var groupUsers = new List<Core.AdminConsole.Entities.GroupUser>(); |
||||
|
||||
for (var i = 0; i < maxRelationships; i++) |
||||
{ |
||||
var orgUserId = organizationUserIds[i]; |
||||
var groupIndex = i % groups.Count; // Round-robin distribution across groups |
||||
|
||||
groupUsers.Add(new Core.AdminConsole.Entities.GroupUser |
||||
{ |
||||
GroupId = groups[groupIndex].Id, |
||||
OrganizationUserId = orgUserId |
||||
}); |
||||
} |
||||
|
||||
return groupUsers; |
||||
} |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
using Bit.Infrastructure.EntityFramework.Repositories; |
||||
|
||||
namespace Bit.Seeder.Recipes; |
||||
|
||||
public class OrganizationDomainRecipe(DatabaseContext db) |
||||
{ |
||||
public void AddVerifiedDomainToOrganization(Guid organizationId, string domainName) |
||||
{ |
||||
var domain = new OrganizationDomain |
||||
{ |
||||
Id = Guid.NewGuid(), |
||||
OrganizationId = organizationId, |
||||
DomainName = domainName, |
||||
Txt = Guid.NewGuid().ToString("N"), |
||||
CreationDate = DateTime.UtcNow, |
||||
}; |
||||
|
||||
domain.SetVerifiedDate(); |
||||
domain.SetLastCheckedDate(); |
||||
|
||||
db.Add(domain); |
||||
db.SaveChanges(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue