mirror of
https://github.com/bitwarden/server
synced 2026-01-02 00:23:40 +00:00
Merge branch 'main' into auth/pm-22975/client-version-validator
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
using System.Net;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
|
||||
|
||||
public class OrganizationUserControllerBulkRevokeTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
|
||||
private Organization _organization = null!;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
public OrganizationUserControllerBulkRevokeTests(ApiApplicationFactory apiFactory)
|
||||
{
|
||||
_factory = apiFactory;
|
||||
_factory.SubstituteService<IFeatureService>(featureService =>
|
||||
{
|
||||
featureService
|
||||
.IsEnabled(FeatureFlagKeys.BulkRevokeUsersV2)
|
||||
.Returns(true);
|
||||
});
|
||||
_client = _factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_ownerEmail = $"org-user-bulk-revoke-test-{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseMonthly,
|
||||
ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_Success()
|
||||
{
|
||||
var (ownerEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
await _loginHelper.LoginAsync(ownerEmail);
|
||||
|
||||
var (_, orgUser1) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
var (_, orgUser2) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [orgUser1.Id, orgUser2.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.NotNull(content);
|
||||
Assert.Equal(2, content.Data.Count());
|
||||
Assert.All(content.Data, r => Assert.Empty(r.Error));
|
||||
|
||||
var actualUsers = await organizationUserRepository.GetManyAsync([orgUser1.Id, orgUser2.Id]);
|
||||
Assert.All(actualUsers, u => Assert.Equal(OrganizationUserStatusType.Revoked, u.Status));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_AsAdmin_Success()
|
||||
{
|
||||
var (adminEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Admin);
|
||||
|
||||
await _loginHelper.LoginAsync(adminEmail);
|
||||
|
||||
var (_, orgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [orgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.NotNull(content);
|
||||
Assert.Single(content.Data);
|
||||
Assert.All(content.Data, r => Assert.Empty(r.Error));
|
||||
|
||||
var actualUser = await _factory.GetService<IOrganizationUserRepository>().GetByIdAsync(orgUser.Id);
|
||||
Assert.NotNull(actualUser);
|
||||
Assert.Equal(OrganizationUserStatusType.Revoked, actualUser.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_CannotRevokeSelf_ReturnsError()
|
||||
{
|
||||
var (userEmail, orgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Admin);
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [orgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.NotNull(content);
|
||||
Assert.Single(content.Data);
|
||||
Assert.Contains(content.Data, r => r.Id == orgUser.Id && r.Error == "You cannot revoke yourself.");
|
||||
|
||||
var actualUser = await _factory.GetService<IOrganizationUserRepository>().GetByIdAsync(orgUser.Id);
|
||||
Assert.NotNull(actualUser);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, actualUser.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_AlreadyRevoked_ReturnsError()
|
||||
{
|
||||
var (ownerEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
await _loginHelper.LoginAsync(ownerEmail);
|
||||
|
||||
var (_, orgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
await organizationUserRepository.RevokeAsync(orgUser.Id);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [orgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.NotNull(content);
|
||||
Assert.Single(content.Data);
|
||||
Assert.Contains(content.Data, r => r.Id == orgUser.Id && r.Error == "Already revoked.");
|
||||
|
||||
var actualUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
||||
Assert.NotNull(actualUser);
|
||||
Assert.Equal(OrganizationUserStatusType.Revoked, actualUser.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_AdminCannotRevokeOwner_ReturnsError()
|
||||
{
|
||||
var (adminEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Admin);
|
||||
|
||||
await _loginHelper.LoginAsync(adminEmail);
|
||||
|
||||
var (_, ownerOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [ownerOrgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.NotNull(content);
|
||||
Assert.Single(content.Data);
|
||||
Assert.Contains(content.Data, r => r.Id == ownerOrgUser.Id && r.Error == "Only owners can revoke other owners.");
|
||||
|
||||
var actualUser = await _factory.GetService<IOrganizationUserRepository>().GetByIdAsync(ownerOrgUser.Id);
|
||||
Assert.NotNull(actualUser);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, actualUser.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_MixedResults()
|
||||
{
|
||||
var (ownerEmail, requestingOwner) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
await _loginHelper.LoginAsync(ownerEmail);
|
||||
|
||||
var (_, validOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
var (_, alreadyRevokedOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
await organizationUserRepository.RevokeAsync(alreadyRevokedOrgUser.Id);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [validOrgUser.Id, alreadyRevokedOrgUser.Id, requestingOwner.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.NotNull(content);
|
||||
Assert.Equal(3, content.Data.Count());
|
||||
|
||||
Assert.Contains(content.Data, r => r.Id == validOrgUser.Id && r.Error == string.Empty);
|
||||
Assert.Contains(content.Data, r => r.Id == alreadyRevokedOrgUser.Id && r.Error == "Already revoked.");
|
||||
Assert.Contains(content.Data, r => r.Id == requestingOwner.Id && r.Error == "You cannot revoke yourself.");
|
||||
|
||||
var actualUsers = await organizationUserRepository.GetManyAsync([validOrgUser.Id, alreadyRevokedOrgUser.Id, requestingOwner.Id]);
|
||||
Assert.Equal(OrganizationUserStatusType.Revoked, actualUsers.First(u => u.Id == validOrgUser.Id).Status);
|
||||
Assert.Equal(OrganizationUserStatusType.Revoked, actualUsers.First(u => u.Id == alreadyRevokedOrgUser.Id).Status);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, actualUsers.First(u => u.Id == requestingOwner.Id).Status);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OrganizationUserType.User)]
|
||||
[InlineData(OrganizationUserType.Custom)]
|
||||
public async Task BulkRevoke_WithoutManageUsersPermission_ReturnsForbidden(OrganizationUserType organizationUserType)
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, organizationUserType, new Permissions { ManageUsers = false });
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [Guid.NewGuid()]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Forbidden, httpResponse.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_WithEmptyIds_ReturnsBadRequest()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = []
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, httpResponse.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_WithInvalidOrganizationId_ReturnsForbidden()
|
||||
{
|
||||
var (ownerEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
await _loginHelper.LoginAsync(ownerEmail);
|
||||
|
||||
var (_, orgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
|
||||
var invalidOrgId = Guid.NewGuid();
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [orgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{invalidOrgId}/users/revoke", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Forbidden, httpResponse.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkRevoke_ProviderRevokesOwner_ReturnsOk()
|
||||
{
|
||||
var providerEmail = $"provider-user{Guid.NewGuid()}@example.com";
|
||||
|
||||
// create user for provider
|
||||
await _factory.LoginWithNewAccount(providerEmail);
|
||||
|
||||
// create provider and provider user
|
||||
await _factory.GetService<ICreateProviderCommand>()
|
||||
.CreateBusinessUnitAsync(
|
||||
new Provider
|
||||
{
|
||||
Name = "provider",
|
||||
Type = ProviderType.BusinessUnit
|
||||
},
|
||||
providerEmail,
|
||||
PlanType.EnterpriseAnnually2023,
|
||||
10);
|
||||
|
||||
await _loginHelper.LoginAsync(providerEmail);
|
||||
|
||||
var providerUserUser = await _factory.GetService<IUserRepository>().GetByEmailAsync(providerEmail);
|
||||
|
||||
var providerUserCollection = await _factory.GetService<IProviderUserRepository>()
|
||||
.GetManyByUserAsync(providerUserUser!.Id);
|
||||
|
||||
var providerUser = providerUserCollection.First();
|
||||
|
||||
await _factory.GetService<IProviderOrganizationRepository>().CreateAsync(new ProviderOrganization
|
||||
{
|
||||
ProviderId = providerUser.ProviderId,
|
||||
OrganizationId = _organization.Id,
|
||||
Key = null,
|
||||
Settings = null
|
||||
});
|
||||
|
||||
var (_, ownerOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [ownerOrgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/revoke", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Bit.Api.AdminConsole.Authorization;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Request.Organizations;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
@@ -14,8 +13,6 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
|
||||
@@ -32,12 +29,6 @@ public class OrganizationUsersControllerPutResetPasswordTests : IClassFixture<Ap
|
||||
public OrganizationUsersControllerPutResetPasswordTests(ApiApplicationFactory apiFactory)
|
||||
{
|
||||
_factory = apiFactory;
|
||||
_factory.SubstituteService<IFeatureService>(featureService =>
|
||||
{
|
||||
featureService
|
||||
.IsEnabled(FeatureFlagKeys.AccountRecoveryCommand)
|
||||
.Returns(true);
|
||||
});
|
||||
_client = _factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -192,6 +192,15 @@ public static class OrganizationTestHelpers
|
||||
await policyRepository.CreateAsync(policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a unique random domain name for testing purposes.
|
||||
/// </summary>
|
||||
/// <returns>A domain string like "a1b2c3d4.com"</returns>
|
||||
public static string GenerateRandomDomain()
|
||||
{
|
||||
return $"{Guid.NewGuid().ToString("N").Substring(0, 8)}.com";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a user account without a Master Password and adds them as a member to the specified organization.
|
||||
/// </summary>
|
||||
|
||||
32
test/Api.IntegrationTest/Helpers/PerformanceTestHelpers.cs
Normal file
32
test/Api.IntegrationTest/Helpers/PerformanceTestHelpers.cs
Normal file
@@ -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,289 @@
|
||||
using System.Net;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.SecretsManager.Enums;
|
||||
using Bit.Api.IntegrationTest.SecretsManager.Helpers;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.SecretsManager.Controllers;
|
||||
|
||||
public class SecretVersionsControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly string _mockEncryptedString =
|
||||
"2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=";
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly ISecretVersionRepository _secretVersionRepository;
|
||||
private readonly IAccessPolicyRepository _accessPolicyRepository;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
|
||||
private string _email = null!;
|
||||
private SecretsManagerOrganizationHelper _organizationHelper = null!;
|
||||
|
||||
public SecretVersionsControllerTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = _factory.CreateClient();
|
||||
_secretRepository = _factory.GetService<ISecretRepository>();
|
||||
_secretVersionRepository = _factory.GetService<ISecretVersionRepository>();
|
||||
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_email);
|
||||
_organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public async Task GetVersionsBySecretId_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/secrets/{secret.Id}/versions");
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetVersionsBySecretId_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
});
|
||||
|
||||
// Create some versions
|
||||
var version1 = await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = _mockEncryptedString,
|
||||
VersionDate = DateTime.UtcNow.AddDays(-2)
|
||||
});
|
||||
|
||||
var version2 = await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = _mockEncryptedString,
|
||||
VersionDate = DateTime.UtcNow.AddDays(-1)
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserSecretAccessPolicy
|
||||
{
|
||||
GrantedSecretId = secret.Id,
|
||||
OrganizationUserId = orgUser.Id,
|
||||
Read = true,
|
||||
Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
var response = await _client.GetAsync($"/secrets/{secret.Id}/versions");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<SecretVersionResponseModel>>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Data.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetVersionById_Success()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
});
|
||||
|
||||
var version = await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = _mockEncryptedString,
|
||||
VersionDate = DateTime.UtcNow
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/secret-versions/{version.Id}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<SecretVersionResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(version.Id, result.Id);
|
||||
Assert.Equal(secret.Id, result.SecretId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RestoreVersion_Success()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = "OriginalValue",
|
||||
Note = _mockEncryptedString
|
||||
});
|
||||
|
||||
var version = await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = "OldValue",
|
||||
VersionDate = DateTime.UtcNow.AddDays(-1)
|
||||
});
|
||||
|
||||
var request = new RestoreSecretVersionRequestModel
|
||||
{
|
||||
VersionId = version.Id
|
||||
};
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/secrets/{secret.Id}/versions/restore", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<SecretResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("OldValue", result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkDelete_Success()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
});
|
||||
|
||||
var version1 = await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = _mockEncryptedString,
|
||||
VersionDate = DateTime.UtcNow.AddDays(-2)
|
||||
});
|
||||
|
||||
var version2 = await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = _mockEncryptedString,
|
||||
VersionDate = DateTime.UtcNow.AddDays(-1)
|
||||
});
|
||||
|
||||
var ids = new List<Guid> { version1.Id, version2.Id };
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secret-versions/delete", ids);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var versions = await _secretVersionRepository.GetManyBySecretIdAsync(secret.Id);
|
||||
Assert.Empty(versions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetVersionsBySecretId_ReturnsOrderedByVersionDate()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
});
|
||||
|
||||
// Create versions in random order
|
||||
await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = "Version2",
|
||||
VersionDate = DateTime.UtcNow.AddDays(-1)
|
||||
});
|
||||
|
||||
await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = "Version3",
|
||||
VersionDate = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await _secretVersionRepository.CreateAsync(new SecretVersion
|
||||
{
|
||||
SecretId = secret.Id,
|
||||
Value = "Version1",
|
||||
VersionDate = DateTime.UtcNow.AddDays(-2)
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/secrets/{secret.Id}/versions");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<SecretVersionResponseModel>>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(3, result.Data.Count());
|
||||
|
||||
var versions = result.Data.ToList();
|
||||
// Should be ordered by VersionDate descending (newest first)
|
||||
Assert.Equal("Version3", versions[0].Value);
|
||||
Assert.Equal("Version2", versions[1].Value);
|
||||
Assert.Equal("Version1", versions[2].Value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user