Skip to content

Commit

Permalink
Added update organisation join request endpoint, usecase & unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
dpatel017 committed Oct 17, 2024
1 parent 63fd076 commit 8e16129
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using CO.CDP.Organisation.WebApi.Model;
using CO.CDP.Organisation.WebApi.UseCase;
using CO.CDP.OrganisationInformation;
using CO.CDP.OrganisationInformation.Persistence;
using FluentAssertions;
using Moq;

namespace CO.CDP.Organisation.WebApi.Tests.UseCase;

public class UpdateJoinRequestUseCaseTests
{
private readonly Mock<IOrganisationRepository> _mockOrganisationRepository;
private readonly Mock<IOrganisationJoinRequestRepository> _mockRequestRepository;
private readonly UpdateJoinRequestUseCase _useCase;
private readonly Guid _organisationId = Guid.NewGuid();
private readonly Guid _joinRequestId = Guid.NewGuid();
public UpdateJoinRequestUseCaseTests()
{
_mockOrganisationRepository = new Mock<IOrganisationRepository>();
_mockRequestRepository = new Mock<IOrganisationJoinRequestRepository>();
_useCase = new UpdateJoinRequestUseCase(_mockOrganisationRepository.Object, _mockRequestRepository.Object);
}

[Fact]
public async Task Execute_ShouldUpdateJoinRequest_WhenOrganisationAndJoinRequestExist()
{
var updateJoinRequest = new UpdateJoinRequest
{
ReviewedBy = 1,
status = OrganisationJoinRequestStatus.Accepted,
};

var joinRequest = OrganisationJoinRequest;

_mockOrganisationRepository
.Setup(repo => repo.Find(_organisationId))
.ReturnsAsync(Organisation);

_mockRequestRepository
.Setup(repo => repo.Find(_joinRequestId, _organisationId))
.ReturnsAsync(joinRequest);

var result = await _useCase.Execute((_organisationId, _joinRequestId, updateJoinRequest));

result.Should().BeTrue();
joinRequest.ReviewedById.Should().Be(updateJoinRequest.ReviewedBy);
joinRequest.Status.Should().Be(updateJoinRequest.status);
joinRequest.ReviewedOn.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(1));

_mockOrganisationRepository.Verify(repo => repo.Find(_organisationId), Times.Once);
_mockRequestRepository.Verify(repo => repo.Find(_joinRequestId, _organisationId), Times.Once);
_mockRequestRepository.Verify(repo => repo.Save(joinRequest), Times.Once);
}

[Fact]
public async Task Execute_ShouldThrowException_WhenOrganisationNotFound()
{
var organisationId = Guid.NewGuid();
var joinRequestId = Guid.NewGuid();
var updateJoinRequest = new UpdateJoinRequest
{
ReviewedBy = 1,
status = OrganisationJoinRequestStatus.Accepted,
};

_mockOrganisationRepository
.Setup(repo => repo.Find(organisationId))
.ReturnsAsync((CO.CDP.OrganisationInformation.Persistence.Organisation?)null);

Func<Task> act = async () => await _useCase.Execute((organisationId, joinRequestId, updateJoinRequest));

await act.Should().ThrowAsync<UnknownOrganisationException>()
.WithMessage($"Unknown organisation {organisationId}.");

_mockOrganisationRepository.Verify(repo => repo.Find(organisationId), Times.Once);
_mockRequestRepository.Verify(repo => repo.Find(It.IsAny<Guid>(), It.IsAny<Guid>()), Times.Never);
_mockRequestRepository.Verify(repo => repo.Save(It.IsAny<CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest>()), Times.Never);
}

[Fact]
public async Task Execute_ShouldThrowException_WhenJoinRequestNotFound()
{
var organisationId = Guid.NewGuid();
var joinRequestId = Guid.NewGuid();
var updateJoinRequest = new UpdateJoinRequest
{
ReviewedBy = 1,
status = OrganisationJoinRequestStatus.Accepted
};

_mockOrganisationRepository
.Setup(repo => repo.Find(organisationId))
.ReturnsAsync(Organisation);

_ = _mockRequestRepository
.Setup(repo => repo.Find(joinRequestId, organisationId))
.ReturnsAsync((CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest?)null);

Func<Task> act = async () => await _useCase.Execute((organisationId, joinRequestId, updateJoinRequest));

await act.Should().ThrowAsync<UnknownOrganisationJoinRequestException>()
.WithMessage($"Unknown organisation join request for org id {organisationId} or request id {joinRequestId}.");

_mockOrganisationRepository.Verify(repo => repo.Find(organisationId), Times.Once);
_mockRequestRepository.Verify(repo => repo.Find(joinRequestId, organisationId), Times.Once);
_mockRequestRepository.Verify(repo => repo.Save(It.IsAny<CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest>()), Times.Never);
}

private CO.CDP.OrganisationInformation.Persistence.Organisation Organisation =>
new CO.CDP.OrganisationInformation.Persistence.Organisation
{
Guid = _organisationId,
Name = "Test",
Tenant = It.IsAny<Tenant>(),
ContactPoints = [new CO.CDP.OrganisationInformation.Persistence.Organisation.ContactPoint { Email = "[email protected]" }],
SupplierInfo = new CO.CDP.OrganisationInformation.Persistence.Organisation.SupplierInformation()
};

private CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest OrganisationJoinRequest =>
new CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest
{
Guid = _joinRequestId,
Status = OrganisationJoinRequestStatus.Accepted,
Organisation = Organisation,
Id = 1
};
}
34 changes: 33 additions & 1 deletion Services/CO.CDP.Organisation.WebApi/Api/Organisation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,38 @@ await useCase.Execute((organisationId, status))
operation.Responses["500"].Description = "Internal server error.";
return operation;
});

app.MapPatch("/organisations/{organisationId}/join-requests/{joinRequestId}",
[OrganisationAuthorize(
[AuthenticationChannel.OneLogin],
[Constants.OrganisationPersonScope.Admin],
OrganisationIdLocation.Path)]
async (Guid organisationId, Guid joinRequestId, UpdateJoinRequest updateJoinRequest, IUseCase<(Guid, Guid, UpdateJoinRequest), bool> useCase) =>
await useCase.Execute((organisationId, joinRequestId, updateJoinRequest))
.AndThen(_ => Results.NoContent())
)
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status204NoContent)
.ProducesProblem(StatusCodes.Status400BadRequest)
.Produces<ProblemDetails>(StatusCodes.Status401Unauthorized)
.ProducesProblem(StatusCodes.Status404NotFound)
.ProducesProblem(StatusCodes.Status422UnprocessableEntity)
.ProducesProblem(StatusCodes.Status500InternalServerError)
.WithOpenApi(operation =>
{
operation.OperationId = "UpdateOrganisationJoinRequest";
operation.Description = "Update an organisation join request.";
operation.Summary = "Update an organisation join request.";
operation.Responses["200"].Description = "Organisation join request updated successfully.";
operation.Responses["204"].Description = "Organisation join request updated successfully.";
operation.Responses["400"].Description = "Bad request.";
operation.Responses["401"].Description = "Valid authentication credentials are missing in the request.";
operation.Responses["404"].Description = "Organisation or join request not found.";
operation.Responses["422"].Description = "Unprocessable entity.";
operation.Responses["500"].Description = "Internal server error.";
return operation;
});
}

public static RouteGroupBuilder UseOrganisationLookupEndpoints(this RouteGroupBuilder app)
Expand Down Expand Up @@ -706,7 +738,7 @@ await useCase.Execute((organisationId, personInviteId))
operation.Responses["500"].Description = "Internal server error.";
return operation;
});

return app;
}

Expand Down
6 changes: 6 additions & 0 deletions Services/CO.CDP.Organisation.WebApi/Model/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,12 @@ public record UpdatePersonToOrganisation
public required List<string> Scopes { get; init; }
}

public record UpdateJoinRequest
{
public OrganisationJoinRequestStatus status { get; init; }
public int ReviewedBy { get; init; }
}

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum SupportOrganisationUpdateType
{
Expand Down
4 changes: 3 additions & 1 deletion Services/CO.CDP.Organisation.WebApi/Model/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ public class MissingOrganisationIdException(string message, Exception? cause = n

public class EmptyAuthenticationKeyNameException(string message, Exception? cause = null) : Exception(message, cause);
public class UnknownAuthenticationKeyException(string message, Exception? cause = null) : Exception(message, cause);
public class InvalidSupportUpdateOrganisationCommand(string message, Exception? cause = null) : Exception(message, cause);
public class InvalidSupportUpdateOrganisationCommand(string message, Exception? cause = null) : Exception(message, cause);

public class UnknownOrganisationJoinRequestException(string message, Exception? cause = null) : Exception(message, cause);
1 change: 1 addition & 0 deletions Services/CO.CDP.Organisation.WebApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
builder.Services.AddScoped<IUseCase<(Guid, string), bool>, RevokeAuthenticationKeyUseCase>();
builder.Services.AddScoped<IUseCase<(Guid, CreateOrganisationJoinRequest), OrganisationJoinRequest>, CreateOrganisationJoinRequestUseCase>();
builder.Services.AddScoped<IUseCase<(Guid, OrganisationJoinRequestStatus?), IEnumerable<OrganisationJoinRequest>>, GetOrganisationJoinRequestUseCase>();
builder.Services.AddScoped<IUseCase<(Guid, Guid, UpdateJoinRequest), bool>, UpdateJoinRequestUseCase>();

builder.Services.AddOrganisationProblemDetails();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using CO.CDP.Organisation.WebApi.Model;
using CO.CDP.OrganisationInformation.Persistence;

namespace CO.CDP.Organisation.WebApi.UseCase;

public class UpdateJoinRequestUseCase(
IOrganisationRepository organisationRepository,
IOrganisationJoinRequestRepository requestRepository)
: IUseCase<(Guid organisationId, Guid joinRequestId, UpdateJoinRequest updateJoinRequest), bool>
{
public async Task<bool> Execute((Guid organisationId, Guid joinRequestId, UpdateJoinRequest updateJoinRequest) command)
{
_ = await organisationRepository.Find(command.organisationId)
?? throw new UnknownOrganisationException($"Unknown organisation {command.organisationId}.");

var joinRequest = await requestRepository.Find(command.joinRequestId, command.organisationId)
?? throw new UnknownOrganisationJoinRequestException ($"Unknown organisation join request for org id {command.organisationId} or request id {command.joinRequestId}.");

joinRequest.ReviewedById = command.updateJoinRequest.ReviewedBy;
joinRequest.Status = command.updateJoinRequest.status;
joinRequest.ReviewedOn = DateTimeOffset.UtcNow;

requestRepository.Save(joinRequest);

return await Task.FromResult(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public void Dispose()
.FirstOrDefaultAsync(t => t.Guid == organisationJoinRequestId);
}

public async Task<OrganisationJoinRequest?> Find(Guid organisationJoinRequestId, Guid organisationId)
{
return await context.OrganisationJoinRequests
.Include(ojr => ojr.Organisation)
.Include(ojr => ojr.Person)
.FirstOrDefaultAsync(t => t.Organisation!.Guid == organisationId && t.Guid == organisationJoinRequestId);
}

public async Task<IEnumerable<OrganisationJoinRequest>> FindByOrganisation(Guid organisationJoinRequestId)
{
return await context.OrganisationJoinRequests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ namespace CO.CDP.OrganisationInformation.Persistence;
public interface IOrganisationJoinRequestRepository : IDisposable
{
Task<OrganisationJoinRequest?> Find(Guid organisationJoinRequestId);

Task<OrganisationJoinRequest?> Find(Guid organisationJoinRequestId, Guid organisationId);
void Save(OrganisationJoinRequest organisationJoinRequest);

Task<IEnumerable<OrganisationJoinRequest>> FindByOrganisation(Guid organisationJoinRequestId);
}

0 comments on commit 8e16129

Please sign in to comment.