Skip to content

Commit c2011b6

Browse files
committed
Add integration tests for OrganizationUsersController's BindOrganization functionality
- Introduced OrganizationUsersControllerBindOrganizationTests to validate the behavior of the GET reset-password-details endpoint. - Implemented tests for successful retrieval of reset password details, handling of non-existent organization users, and cases where the user belongs to a different organization. - Ensured comprehensive coverage of scenarios to verify correct status responses and organization binding logic.
1 parent 0fdb651 commit c2011b6

1 file changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System.Net;
2+
using Bit.Api.IntegrationTest.Factories;
3+
using Bit.Api.IntegrationTest.Helpers;
4+
using Bit.Core.Billing.Enums;
5+
using Bit.Core.Enums;
6+
using Bit.Core.Repositories;
7+
using Xunit;
8+
9+
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
10+
11+
/// <summary>
12+
/// Integration tests for <see cref="Bit.Api.AdminConsole.Attributes.BindOrganizationAttribute"/>,
13+
/// exercised through the GET reset-password-details endpoint which binds an Organization from the
14+
/// <c>orgId</c> route parameter.
15+
/// </summary>
16+
public class OrganizationUsersControllerBindOrganizationTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
17+
{
18+
private readonly ApiApplicationFactory _factory;
19+
private readonly HttpClient _client;
20+
private readonly LoginHelper _loginHelper;
21+
22+
private string _ownerEmail = null!;
23+
24+
public OrganizationUsersControllerBindOrganizationTests(ApiApplicationFactory factory)
25+
{
26+
_factory = factory;
27+
_client = _factory.CreateClient();
28+
_loginHelper = new LoginHelper(_factory, _client);
29+
}
30+
31+
public async Task InitializeAsync()
32+
{
33+
_ownerEmail = $"bind-org-test-{Guid.NewGuid()}@example.com";
34+
await _factory.LoginWithNewAccount(_ownerEmail);
35+
}
36+
37+
public Task DisposeAsync()
38+
{
39+
_client.Dispose();
40+
return Task.CompletedTask;
41+
}
42+
43+
[Fact]
44+
public async Task GetResetPasswordDetails_HappyPath_ReturnsOk()
45+
{
46+
// Arrange
47+
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
48+
plan: PlanType.EnterpriseAnnually,
49+
ownerEmail: _ownerEmail,
50+
passwordManagerSeats: 10,
51+
paymentMethod: PaymentMethodType.Card);
52+
53+
var organizationRepository = _factory.GetService<IOrganizationRepository>();
54+
organization.UseResetPassword = true;
55+
await organizationRepository.ReplaceAsync(organization);
56+
57+
await _loginHelper.LoginAsync(_ownerEmail);
58+
59+
var (_, memberOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(
60+
_factory, organization.Id, OrganizationUserType.User);
61+
62+
var orgUserRepository = _factory.GetService<IOrganizationUserRepository>();
63+
memberOrgUser.ResetPasswordKey = "encrypted-reset-password-key";
64+
await orgUserRepository.ReplaceAsync(memberOrgUser);
65+
66+
// Act
67+
var response = await _client.GetAsync(
68+
$"organizations/{organization.Id}/users/{memberOrgUser.Id}/reset-password-details");
69+
70+
// Assert
71+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
72+
73+
await organizationRepository.DeleteAsync(organization);
74+
}
75+
76+
[Fact]
77+
public async Task GetResetPasswordDetails_OrgUserNotFound_ReturnsNotFound()
78+
{
79+
// Arrange — org exists and auth passes, but the org user ID in the path does not exist.
80+
// BindOrganizationAttribute successfully binds the org; the endpoint then throws
81+
// NotFoundException because the repository returns null for the unknown org user ID.
82+
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
83+
plan: PlanType.EnterpriseAnnually,
84+
ownerEmail: _ownerEmail,
85+
passwordManagerSeats: 10,
86+
paymentMethod: PaymentMethodType.Card);
87+
88+
var organizationRepository = _factory.GetService<IOrganizationRepository>();
89+
organization.UseResetPassword = true;
90+
await organizationRepository.ReplaceAsync(organization);
91+
92+
await _loginHelper.LoginAsync(_ownerEmail);
93+
94+
// Act — use a random Guid that has no matching OrganizationUser row
95+
var response = await _client.GetAsync(
96+
$"organizations/{organization.Id}/users/{Guid.NewGuid()}/reset-password-details");
97+
98+
// Assert
99+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
100+
101+
await organizationRepository.DeleteAsync(organization);
102+
}
103+
104+
[Fact]
105+
public async Task GetResetPasswordDetails_OrgUserBelongsToDifferentOrg_ReturnsNotFound()
106+
{
107+
// Arrange — create two separate organizations
108+
var (org1, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
109+
plan: PlanType.EnterpriseAnnually,
110+
ownerEmail: _ownerEmail,
111+
passwordManagerSeats: 10,
112+
paymentMethod: PaymentMethodType.Card);
113+
114+
var secondOwnerEmail = $"bind-org-test-owner2-{Guid.NewGuid()}@example.com";
115+
await _factory.LoginWithNewAccount(secondOwnerEmail);
116+
117+
var (org2, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
118+
plan: PlanType.EnterpriseAnnually,
119+
ownerEmail: secondOwnerEmail,
120+
passwordManagerSeats: 10,
121+
paymentMethod: PaymentMethodType.Card);
122+
123+
var organizationRepository = _factory.GetService<IOrganizationRepository>();
124+
org1.UseResetPassword = true;
125+
await organizationRepository.ReplaceAsync(org1);
126+
127+
// Create a user in org2
128+
var (_, org2MemberOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(
129+
_factory, org2.Id, OrganizationUserType.User);
130+
131+
// Log in as owner of org1 (who has ManageAccountRecovery for org1)
132+
await _loginHelper.LoginAsync(_ownerEmail);
133+
134+
// Act — request org1's endpoint but pass an org user ID that belongs to org2
135+
var response = await _client.GetAsync(
136+
$"organizations/{org1.Id}/users/{org2MemberOrgUser.Id}/reset-password-details");
137+
138+
// Assert — the org user's OrganizationId does not match org1, so NotFoundException is thrown
139+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
140+
141+
await organizationRepository.DeleteAsync(org1);
142+
await organizationRepository.DeleteAsync(org2);
143+
}
144+
}

0 commit comments

Comments
 (0)