Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions backend/openapi/UKPS.Api.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,26 @@
}
}
},
"403": {
"description": "Forbidden",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
Expand Down Expand Up @@ -314,6 +334,26 @@
}
}
},
"403": {
"description": "Forbidden",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
Expand Down
8 changes: 8 additions & 0 deletions backend/src/Controllers/OrganisationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ CancellationToken cancellationToken
Name = nameof(DeactivateMembership)
)]
[ProducesResponseType<OrganisationMembershipDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<OrganisationMembershipDto>> DeactivateMembership(
int organisationId,
Expand All @@ -93,6 +94,9 @@ CancellationToken cancellationToken
OrganisationMembershipDeactivateUserError.NotFound => NotFound(
$"Could not find a membership with organisation ID = {organisationId} and membership ID = {membershipId}."
),
OrganisationMembershipDeactivateUserError.NotAllowed => Forbid(
"The user is not authorised to perform this action."
),
_ => throw new UnreachableException(),
}
);
Expand All @@ -103,6 +107,7 @@ CancellationToken cancellationToken
Name = nameof(UpdateUserRole)
)]
[ProducesResponseType<OrganisationMembershipDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<OrganisationMembershipDto>> UpdateUserRole(
int organisationId,
Expand All @@ -125,6 +130,9 @@ CancellationToken cancellationToken
OrganisationMembershipUpdateUserRoleError.NotFound notFound => NotFound(
$"Could not find a membership with organisation ID = {notFound.OrganisationId} and membership ID = {notFound.MembershipId}."
),
OrganisationMembershipUpdateUserRoleError.NotAllowed => Forbid(
"The user is not authorised to perform this action."
),
_ => throw new UnreachableException(),
}
);
Expand Down
53 changes: 53 additions & 0 deletions backend/tests/Controllers/OrganisationControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
using UKPS.Api.Enums;
using UKPS.Api.Services.Errors;
using UKPS.Api.Services.Interfaces;
using DeactivateUserMembershipResult = UKPS.Api.Common.Result<
UKPS.Api.DTOs.OrganisationMembershipDto,
UKPS.Api.Services.Errors.OrganisationMembershipDeactivateUserError
>;
using UpdateUserRoleResult = UKPS.Api.Common.Result<
UKPS.Api.DTOs.OrganisationMembershipDto,
UKPS.Api.Services.Errors.OrganisationMembershipUpdateUserRoleError
>;

namespace UKPS.Api.Tests.Controllers;

Expand All @@ -17,11 +25,14 @@ public class OrganisationControllerTests
private static readonly DateTime _createdAt = new(2026, 6, 19, 12, 50, 1, DateTimeKind.Utc);
private static readonly DateTime _lastActive = new(2026, 6, 20, 12, 50, 1, DateTimeKind.Utc);
private readonly IOrganisationService _organisationServiceMock;
private readonly IOrganisationMembershipService _organisationMembershipService;
private readonly OrganisationController _controller;

public OrganisationControllerTests()
{
_organisationMembershipService = Substitute.For<IOrganisationMembershipService>();
_organisationServiceMock = Substitute.For<IOrganisationService>();
_organisationServiceMock.Memberships.Returns(_organisationMembershipService);

_organisationServiceMock
.GetOrganisationById(Arg.Any<int>(), TestContext.Current.CancellationToken)
Expand Down Expand Up @@ -264,6 +275,48 @@ public void UpdateOrganisationDetailsDto_EmailIsInvalid_IsInvalid()
);
}

[Fact]
public async Task DeactivateMembership_UserIsNotAuthorised_ReturnsForbidResult()
{
_organisationMembershipService
.DeactivateMembership(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(
DeactivateUserMembershipResult.Err(
new OrganisationMembershipDeactivateUserError.NotAllowed(1)
)
);
ActionResult<OrganisationMembershipDto> result = await _controller.DeactivateMembership(
1,
1,
TestContext.Current.CancellationToken
);
result.Result.ShouldBeOfType<ForbidResult>();
}

[Fact]
public async Task UpdateUserRole_UserIsNotAuthorised_ReturnsForbidResult()
{
_organisationMembershipService
.UpdateUserRole(
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<UpdateOrgMembershipUserRoleCommandDto>(),
Arg.Any<CancellationToken>()
)
.Returns(
UpdateUserRoleResult.Err(
new OrganisationMembershipUpdateUserRoleError.NotAllowed(1)
)
);
ActionResult<OrganisationMembershipDto> result = await _controller.UpdateUserRole(
1,
1,
new UpdateOrgMembershipUserRoleCommandDto() { UserRole = UserRole.Standard },
TestContext.Current.CancellationToken
);
result.Result.ShouldBeOfType<ForbidResult>();
}

private static OrganisationDetailsDto CreateOrganisationDetailsDto() =>
new()
{
Expand Down