mirror of
https://github.com/bitwarden/server.git
synced 2026-06-01 11:45:20 -05:00
* Implement GetOrganizationInviteLinkStatusQuery to retrieve invite link status - Added GetOrganizationInviteLinkStatusQuery class to handle fetching the status of an organization invite link based on its code. - Introduced OrganizationInviteLinkStatus and OrganizationInviteLinkSsoStatus records to encapsulate the invite link status and SSO information. - Created IGetOrganizationInviteLinkStatusQuery interface to define the contract for the query implementation. * Add unit tests for GetOrganizationInviteLinkStatusQuery - Introduced comprehensive unit tests for GetOrganizationInviteLinkStatusQuery to validate various scenarios including successful retrieval of invite link status, handling of not found errors, and seat availability checks. - Utilized Xunit and NSubstitute for testing and mocking dependencies, ensuring robust coverage of the query's functionality. * Add IGetOrganizationInviteLinkStatusQuery to service collection - Registered IGetOrganizationInviteLinkStatusQuery with the service collection to enable retrieval of organization invite link status. - This addition supports the recently implemented GetOrganizationInviteLinkStatusQuery functionality. * Add OrganizationInviteLinksPublicController and response models - Introduced OrganizationInviteLinksPublicController to handle requests for organization invite link status. - Implemented GetStatus endpoint to retrieve the status of an invite link using its GUID code. - Added OrganizationInviteLinkStatusResponseModel and OrganizationInviteLinkSsoResponseModel to structure the response data for the invite link status. - Ensured the endpoint is accessible to anonymous users while requiring application authorization for other actions. * Add integration tests for OrganizationInviteLinksPublicController - Introduced integration tests for OrganizationInviteLinksPublicController to validate the GetStatus endpoint functionality. - Implemented tests to ensure correct handling of existing invite links and appropriate responses for valid and not found scenarios. - Utilized Xunit and NSubstitute for testing and mocking dependencies, enhancing test coverage for invite link status retrieval. * Updated GetOrganizationInviteLinkStatusQuery to return SSO status based on organization settings, including UseSso and UsePolicies * Move status endpoint into OrganizationInviteLinksController as POST * Refactor OrganizationInviteLinkStatusResponseModel and OrganizationInviteLinkStatus to remove OrganizationId property - Removed OrganizationId property from both OrganizationInviteLinkStatusResponseModel and OrganizationInviteLinkStatus records to streamline the data model. - Updated constructors accordingly to reflect the changes in the response models. * Refactor GetOrganizationInviteLinkStatusQuery to simplify organization checks - Updated the logic in GetOrganizationInviteLinkStatusQuery to streamline organization validation by combining null and enabled checks. - Removed the dependency on IApplicationCacheService and adjusted the seat availability logic to enhance clarity and efficiency. - Modified the return statement to use organization name directly instead of organization ID. * Add integration tests for OrganizationInviteLinksController - Introduced a new test method to validate the GetStatus functionality for existing invite links in OrganizationInviteLinksControllerTests. - Enhanced existing tests to ensure correct responses for valid and not found scenarios. - Removed OrganizationInviteLinksPublicControllerTests as its functionality is now covered in the OrganizationInviteLinksControllerTests. * Refactor OrganizationInviteLinksControllerTests - Updated test methods in OrganizationInviteLinksControllerTests to utilize GetOrganizationInviteLinkStatusRequestModel instead of individual parameters. - Added a new test case to handle scenarios where the invite link status is not available, returning a BadRequest response. - Enhanced existing tests to ensure consistent handling of valid and not found scenarios. * Update GetOrganizationInviteLinkStatusQueryTests to enable organization for invite link tests
266 lines
10 KiB
C#
266 lines
10 KiB
C#
using Bit.Api.AdminConsole.Controllers;
|
|
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
|
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.InviteLinks;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.InviteLinks.Interfaces;
|
|
using Bit.Core.AdminConsole.Utilities.v2.Results;
|
|
using Bit.Test.Common.AutoFixture;
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http.HttpResults;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
using ErrorResponseModel = Bit.Core.Models.Api.ErrorResponseModel;
|
|
|
|
namespace Bit.Api.Test.AdminConsole.Controllers;
|
|
|
|
[ControllerCustomize(typeof(OrganizationInviteLinksController))]
|
|
[SutProviderCustomize]
|
|
public class OrganizationInviteLinksControllerTests
|
|
{
|
|
[Theory, BitAutoData]
|
|
public async Task Create_WithValidInput_Success(
|
|
Guid orgId,
|
|
OrganizationInviteLink inviteLink,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
inviteLink.OrganizationId = orgId;
|
|
inviteLink.AllowedDomains = "[\"acme.com\"]";
|
|
|
|
var model = new CreateOrganizationInviteLinkRequestModel
|
|
{
|
|
AllowedDomains = ["acme.com"],
|
|
EncryptedInviteKey = "encrypted-key",
|
|
};
|
|
|
|
sutProvider.GetDependency<ICreateOrganizationInviteLinkCommand>()
|
|
.CreateAsync(Arg.Any<CreateOrganizationInviteLinkRequest>())
|
|
.Returns(new CommandResult<OrganizationInviteLink>(inviteLink));
|
|
|
|
var result = await sutProvider.Sut.Create(orgId, model);
|
|
|
|
var createdResult = Assert.IsType<Created<OrganizationInviteLinkResponseModel>>(result);
|
|
Assert.Equal($"organizations/{orgId}/invite-link", createdResult.Location);
|
|
Assert.NotNull(createdResult.Value);
|
|
Assert.Equal(inviteLink.Id, createdResult.Value.Id);
|
|
Assert.Equal(inviteLink.Code, createdResult.Value.Code);
|
|
Assert.Equal(orgId, createdResult.Value.OrganizationId);
|
|
|
|
await sutProvider.GetDependency<ICreateOrganizationInviteLinkCommand>()
|
|
.Received(1)
|
|
.CreateAsync(Arg.Is<CreateOrganizationInviteLinkRequest>(r =>
|
|
r.OrganizationId == orgId &&
|
|
r.EncryptedInviteKey == "encrypted-key"));
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Create_WithExistingLink_Returns409(
|
|
Guid orgId,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
var model = new CreateOrganizationInviteLinkRequestModel
|
|
{
|
|
AllowedDomains = ["acme.com"],
|
|
EncryptedInviteKey = "encrypted-key",
|
|
};
|
|
|
|
sutProvider.GetDependency<ICreateOrganizationInviteLinkCommand>()
|
|
.CreateAsync(Arg.Any<CreateOrganizationInviteLinkRequest>())
|
|
.Returns(new CommandResult<OrganizationInviteLink>(new InviteLinkAlreadyExists()));
|
|
|
|
var result = await sutProvider.Sut.Create(orgId, model);
|
|
|
|
var jsonResult = Assert.IsType<JsonHttpResult<Bit.Core.Models.Api.ErrorResponseModel>>(result);
|
|
Assert.Equal(StatusCodes.Status409Conflict, jsonResult.StatusCode);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Get_WhenLinkExists_ReturnsOkWithModel(
|
|
Guid orgId,
|
|
OrganizationInviteLink inviteLink,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
inviteLink.OrganizationId = orgId;
|
|
inviteLink.AllowedDomains = "[\"acme.com\"]";
|
|
|
|
sutProvider.GetDependency<IGetOrganizationInviteLinkQuery>()
|
|
.GetAsync(orgId)
|
|
.Returns(new CommandResult<OrganizationInviteLink>(inviteLink));
|
|
|
|
var result = await sutProvider.Sut.Get(orgId);
|
|
|
|
var okResult = Assert.IsType<Ok<OrganizationInviteLinkResponseModel>>(result);
|
|
Assert.NotNull(okResult.Value);
|
|
Assert.Equal(inviteLink.Id, okResult.Value.Id);
|
|
Assert.Equal(orgId, okResult.Value.OrganizationId);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Get_WhenNoLinkExists_ReturnsNotFound(
|
|
Guid orgId,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IGetOrganizationInviteLinkQuery>()
|
|
.GetAsync(orgId)
|
|
.Returns(new CommandResult<OrganizationInviteLink>(new InviteLinkNotFound()));
|
|
|
|
var result = await sutProvider.Sut.Get(orgId);
|
|
|
|
var notFoundResult = Assert.IsType<NotFound<Bit.Core.Models.Api.ErrorResponseModel>>(result);
|
|
Assert.NotNull(notFoundResult.Value);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Get_WhenInviteLinkNotAvailable_Returns400(
|
|
Guid orgId,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IGetOrganizationInviteLinkQuery>()
|
|
.GetAsync(orgId)
|
|
.Returns(new CommandResult<OrganizationInviteLink>(new InviteLinkNotAvailable()));
|
|
|
|
var result = await sutProvider.Sut.Get(orgId);
|
|
|
|
var badRequestResult = Assert.IsType<BadRequest<Bit.Core.Models.Api.ErrorResponseModel>>(result);
|
|
Assert.NotNull(badRequestResult.Value);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Create_WithValidationError_Returns400(
|
|
Guid orgId,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
var model = new CreateOrganizationInviteLinkRequestModel
|
|
{
|
|
AllowedDomains = [],
|
|
EncryptedInviteKey = "encrypted-key",
|
|
};
|
|
|
|
sutProvider.GetDependency<ICreateOrganizationInviteLinkCommand>()
|
|
.CreateAsync(Arg.Any<CreateOrganizationInviteLinkRequest>())
|
|
.Returns(new CommandResult<OrganizationInviteLink>(new InviteLinkDomainsRequired()));
|
|
|
|
var result = await sutProvider.Sut.Create(orgId, model);
|
|
|
|
var badRequestResult = Assert.IsType<BadRequest<Bit.Core.Models.Api.ErrorResponseModel>>(result);
|
|
Assert.NotNull(badRequestResult.Value);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Update_WithValidInput_ReturnsOk(
|
|
Guid orgId,
|
|
OrganizationInviteLink inviteLink,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
inviteLink.OrganizationId = orgId;
|
|
inviteLink.AllowedDomains = "[\"acme.com\"]";
|
|
|
|
var model = new UpdateOrganizationInviteLinkRequestModel
|
|
{
|
|
AllowedDomains = ["acme.com"],
|
|
};
|
|
|
|
sutProvider.GetDependency<IUpdateOrganizationInviteLinkCommand>()
|
|
.UpdateAsync(Arg.Any<UpdateOrganizationInviteLinkRequest>())
|
|
.Returns(new CommandResult<OrganizationInviteLink>(inviteLink));
|
|
|
|
var result = await sutProvider.Sut.Update(orgId, model);
|
|
|
|
var okResult = Assert.IsType<Ok<OrganizationInviteLinkResponseModel>>(result);
|
|
Assert.NotNull(okResult.Value);
|
|
Assert.Equal(inviteLink.Id, okResult.Value.Id);
|
|
Assert.Equal(orgId, okResult.Value.OrganizationId);
|
|
|
|
await sutProvider.GetDependency<IUpdateOrganizationInviteLinkCommand>()
|
|
.Received(1)
|
|
.UpdateAsync(Arg.Is<UpdateOrganizationInviteLinkRequest>(r =>
|
|
r.OrganizationId == orgId));
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Update_WhenNoLinkExists_ReturnsNotFound(
|
|
Guid orgId,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
var model = new UpdateOrganizationInviteLinkRequestModel
|
|
{
|
|
AllowedDomains = ["acme.com"],
|
|
};
|
|
|
|
sutProvider.GetDependency<IUpdateOrganizationInviteLinkCommand>()
|
|
.UpdateAsync(Arg.Any<UpdateOrganizationInviteLinkRequest>())
|
|
.Returns(new CommandResult<OrganizationInviteLink>(new InviteLinkNotFound()));
|
|
|
|
var result = await sutProvider.Sut.Update(orgId, model);
|
|
|
|
var notFoundResult = Assert.IsType<NotFound<Bit.Core.Models.Api.ErrorResponseModel>>(result);
|
|
Assert.NotNull(notFoundResult.Value);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task Update_WithValidationError_Returns400(
|
|
Guid orgId,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
var model = new UpdateOrganizationInviteLinkRequestModel
|
|
{
|
|
AllowedDomains = [],
|
|
};
|
|
|
|
sutProvider.GetDependency<IUpdateOrganizationInviteLinkCommand>()
|
|
.UpdateAsync(Arg.Any<UpdateOrganizationInviteLinkRequest>())
|
|
.Returns(new CommandResult<OrganizationInviteLink>(new InviteLinkDomainsRequired()));
|
|
|
|
var result = await sutProvider.Sut.Update(orgId, model);
|
|
|
|
var badRequestResult = Assert.IsType<BadRequest<ErrorResponseModel>>(result);
|
|
Assert.NotNull(badRequestResult.Value);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task GetStatus_WithValidQuery_Success(
|
|
GetOrganizationInviteLinkStatusRequestModel model,
|
|
OrganizationInviteLinkStatus status,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IGetOrganizationInviteLinkStatusQuery>()
|
|
.GetStatusAsync(model.Code)
|
|
.Returns(new CommandResult<OrganizationInviteLinkStatus>(status));
|
|
|
|
var result = await sutProvider.Sut.GetStatus(model);
|
|
|
|
var okResult = Assert.IsType<Ok<OrganizationInviteLinkStatusResponseModel>>(result);
|
|
Assert.Equal(status.OrganizationName, okResult.Value!.OrganizationName);
|
|
Assert.Equal(status.SeatsAvailable, okResult.Value.SeatsAvailable);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task GetStatus_WithNotFoundError_ReturnsNotFound(
|
|
GetOrganizationInviteLinkStatusRequestModel model,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IGetOrganizationInviteLinkStatusQuery>()
|
|
.GetStatusAsync(model.Code)
|
|
.Returns(new CommandResult<OrganizationInviteLinkStatus>(new InviteLinkNotFound()));
|
|
|
|
var result = await sutProvider.Sut.GetStatus(model);
|
|
|
|
Assert.IsType<NotFound<ErrorResponseModel>>(result);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task GetStatus_WithNotAvailableError_ReturnsBadRequest(
|
|
GetOrganizationInviteLinkStatusRequestModel model,
|
|
SutProvider<OrganizationInviteLinksController> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IGetOrganizationInviteLinkStatusQuery>()
|
|
.GetStatusAsync(model.Code)
|
|
.Returns(new CommandResult<OrganizationInviteLinkStatus>(new InviteLinkNotAvailable()));
|
|
|
|
var result = await sutProvider.Sut.GetStatus(model);
|
|
|
|
Assert.IsType<BadRequest<ErrorResponseModel>>(result);
|
|
}
|
|
}
|