Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package uk.gov.hmcts.reform.fact.functional.controllers;

import io.qameta.allure.Feature;
import io.restassured.response.Response;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import uk.gov.hmcts.reform.fact.data.api.entities.Approval;
import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType;
import uk.gov.hmcts.reform.fact.functional.helpers.AssertionHelper;
import uk.gov.hmcts.reform.fact.functional.helpers.TestDataHelper;
import uk.gov.hmcts.reform.fact.functional.http.HttpClient;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;

@Feature("Approval Controller")
@DisplayName("Approval Controller")
public final class ApprovalControllerFunctionalTest {

private static final HttpClient http = new HttpClient();
private static final List<UUID> approvalIdsToDelete = new CopyOnWriteArrayList<>();

@Test
@DisplayName("POST /approvals/v1 creates approval and GET /approvals/v1 returns it")
void shouldCreateAndReturnApprovalSuccessfully() {
final UUID courtId = TestDataHelper.createCourt(http, "Test Court Approval");
final UUID userId = TestDataHelper.createUser(http, "test.approval.create");
final Approval approval = createApproval(courtId, AuditSubjectType.COURT, userId);

final Response createResponse = http.doPost("/approvals/v1", approval);
AssertionHelper.assertStatus(createResponse, CREATED);

final UUID approvalId = UUID.fromString(createResponse.jsonPath().getString("id"));
approvalIdsToDelete.add(approvalId);

assertThat(createResponse.jsonPath().getString("subjectId"))
.as("Created approval should reference the requested court")
.isEqualTo(courtId.toString());
assertThat(createResponse.jsonPath().getString("subjectType"))
.as("Created approval should reference a court subject")
.isEqualTo(AuditSubjectType.COURT.name());
assertThat(createResponse.jsonPath().getString("userId"))
.as("Created approval should reference the requested user")
.isEqualTo(userId.toString());
assertThat(createResponse.jsonPath().getString("lastUpdatedAt"))
.as("Created approval should include a last updated timestamp")
.isNotBlank();

final Response getResponse = http.doGet("/approvals/v1");
AssertionHelper.assertStatus(getResponse, OK);

final String approvalPath = "find { it.subjectId == '" + courtId + "' && it.subjectType == 'COURT' }";
assertThat(getResponse.jsonPath().getString(approvalPath + ".name"))
.as("GET approvals should include the created court")
.isNotBlank();
assertThat(getResponse.jsonPath().getBoolean(approvalPath + ".approved"))
.as("GET approvals should mark the created court as approved")
.isTrue();
assertThat(getResponse.jsonPath().getString(approvalPath + ".approvalId"))
.as("GET approvals should include the approval ID for deleting")
.isEqualTo(approvalId.toString());
assertThat(getResponse.jsonPath().getString(approvalPath + ".user.id"))
.as("GET approvals should include the approver user")
.isEqualTo(userId.toString());
assertThat(getResponse.jsonPath().getString(approvalPath + ".user.email"))
.as("GET approvals should include the approver email")
.startsWith("test.approval.create.");
}

@Test
@DisplayName("POST /approvals/v1 creates service centre approval")
void shouldCreateServiceCentreApprovalSuccessfully() {
final UUID serviceCentreId = TestDataHelper.createServiceCentre(http, "Test Service Centre Approval");
final UUID userId = TestDataHelper.createUser(http, "test.approval.service.centre");
final Approval approval = createApproval(serviceCentreId, AuditSubjectType.SERVICE_CENTRE, userId);

final Response createResponse = http.doPost("/approvals/v1", approval);
AssertionHelper.assertStatus(createResponse, CREATED);

final UUID approvalId = UUID.fromString(createResponse.jsonPath().getString("id"));
approvalIdsToDelete.add(approvalId);

assertThat(createResponse.jsonPath().getString("subjectId"))
.as("Created approval should reference the requested service centre")
.isEqualTo(serviceCentreId.toString());
assertThat(createResponse.jsonPath().getString("subjectType"))
.as("Created approval should reference a service centre subject")
.isEqualTo(AuditSubjectType.SERVICE_CENTRE.name());
}

@Test
@DisplayName("DELETE /approvals/{approvalId}/v1 deletes approval")
void shouldDeleteApprovalSuccessfully() {
final UUID courtId = TestDataHelper.createCourt(http, "Test Court Approval Delete");
final UUID userId = TestDataHelper.createUser(http, "test.approval.delete");
final Response createResponse = http.doPost("/approvals/v1", createApproval(courtId, AuditSubjectType.COURT,
userId));
AssertionHelper.assertStatus(createResponse, CREATED);

final UUID approvalId = UUID.fromString(createResponse.jsonPath().getString("id"));
approvalIdsToDelete.add(approvalId);

final Response deleteResponse = http.doDelete("/approvals/" + approvalId + "/v1");
AssertionHelper.assertStatus(deleteResponse, NO_CONTENT);
approvalIdsToDelete.remove(approvalId);

final Response secondDeleteResponse = http.doDelete("/approvals/" + approvalId + "/v1");
AssertionHelper.assertStatus(secondDeleteResponse, NOT_FOUND);
}

@Test
@DisplayName("POST /approvals/v1 returns 400 for invalid approval")
void shouldReturnBadRequestForInvalidApproval() {
final Approval approval = Approval.builder()
.subjectId(UUID.randomUUID())
.subjectType(AuditSubjectType.COURT)
.build();

final Response response = http.doPost("/approvals/v1", approval);
AssertionHelper.assertStatus(response, BAD_REQUEST);
}

@Test
@DisplayName("POST /approvals/v1 returns 404 when subject does not exist")
void shouldReturnNotFoundWhenSubjectDoesNotExist() {
final UUID userId = TestDataHelper.createUser(http, "test.approval.missing.subject");
final Approval approval = createApproval(UUID.randomUUID(), AuditSubjectType.COURT, userId);

final Response response = http.doPost("/approvals/v1", approval);
AssertionHelper.assertStatus(response, NOT_FOUND);

assertThat(response.jsonPath().getString("message"))
.as("Error message should mention court not found")
.contains("Court not found");
}

@Test
@DisplayName("DELETE /approvals/{approvalId}/v1 returns 404 for unknown approval")
void shouldReturnNotFoundWhenDeletingUnknownApproval() {
final Response response = http.doDelete("/approvals/" + UUID.randomUUID() + "/v1");
AssertionHelper.assertStatus(response, NOT_FOUND);
}

private static Approval createApproval(final UUID subjectId, final AuditSubjectType subjectType,
final UUID userId) {
return Approval.builder()
.subjectId(subjectId)
.subjectType(subjectType)
.userId(userId)
.build();
}

@AfterAll
static void cleanUp() {
approvalIdsToDelete.forEach(approvalId -> http.doDelete("/approvals/" + approvalId + "/v1"));
http.doDelete("/testing-support/courts/name-prefix/Test Court Approval");
http.doDelete("/testing-support/service-centres/name-prefix/Test Service Centre Approval");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package uk.gov.hmcts.reform.fact.data.api.controllers;

import tools.jackson.databind.ObjectMapper;
import uk.gov.hmcts.reform.fact.data.api.dto.ApprovalStatus;
import uk.gov.hmcts.reform.fact.data.api.entities.Approval;
import uk.gov.hmcts.reform.fact.data.api.entities.User;
import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType;
import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException;
import uk.gov.hmcts.reform.fact.data.api.services.ApprovalService;

import java.util.List;
import java.util.UUID;

import io.qameta.allure.Feature;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@Feature("Approval Controller")
@DisplayName("Approval Controller")
@WebMvcTest(ApprovalController.class)
@AutoConfigureMockMvc(addFilters = false)
class ApprovalControllerTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@MockitoBean
private ApprovalService approvalService;

private final UUID approvalId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
private final UUID subjectId = UUID.fromString("222e4567-e89b-12d3-a456-426614174000");
private final UUID userId = UUID.fromString("333e4567-e89b-12d3-a456-426614174000");

@Test
@DisplayName("GET /approvals/v1 returns approvals successfully")
void getAllApprovalsReturnsSuccessfully() throws Exception {
when(approvalService.getAllApprovalStatuses()).thenReturn(List.of(createApprovalStatus()));

mockMvc.perform(get("/approvals/v1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$[0].subjectId").value(subjectId.toString()))
.andExpect(jsonPath("$[0].subjectType").value(AuditSubjectType.COURT.name()))
.andExpect(jsonPath("$[0].name").value("Test Court"))
.andExpect(jsonPath("$[0].approved").value(true))
.andExpect(jsonPath("$[0].approvalId").value(approvalId.toString()))
.andExpect(jsonPath("$[0].userId").value(userId.toString()))
.andExpect(jsonPath("$[0].user.id").value(userId.toString()))
.andExpect(jsonPath("$[0].user.email").value("approver@justice.gov.uk"));
}

@Test
@DisplayName("POST /approvals/v1 creates approval successfully")
void createApprovalSuccessfully() throws Exception {
Approval approval = createApproval();
when(approvalService.createApproval(any(Approval.class))).thenReturn(approval);

mockMvc.perform(post("/approvals/v1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(approval)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(approvalId.toString()));
}

@Test
@DisplayName("POST /approvals/v1 returns 400 for invalid request body")
void createApprovalWithInvalidBodyReturnsBadRequest() throws Exception {
Approval approval = createApproval();
approval.setSubjectId(null);

mockMvc.perform(post("/approvals/v1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(approval)))
.andExpect(status().isBadRequest());
}

@Test
@DisplayName("POST /approvals/v1 returns 404 when user or subject does not exist")
void createApprovalWhenReferenceDoesNotExistReturnsNotFound() throws Exception {
Approval approval = createApproval();
when(approvalService.createApproval(any(Approval.class))).thenThrow(new NotFoundException("User not found"));

mockMvc.perform(post("/approvals/v1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(approval)))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("DELETE /approvals/{approvalId}/v1 deletes approval successfully")
void deleteApprovalSuccessfully() throws Exception {
mockMvc.perform(delete("/approvals/{approvalId}/v1", approvalId))
.andExpect(status().isNoContent());
}

@Test
@DisplayName("DELETE /approvals/{approvalId}/v1 returns 400 for invalid UUID")
void deleteApprovalWithInvalidUuidReturnsBadRequest() throws Exception {
mockMvc.perform(delete("/approvals/{approvalId}/v1", "invalid-uuid"))
.andExpect(status().isBadRequest());
}

@Test
@DisplayName("DELETE /approvals/{approvalId}/v1 returns 404 when approval does not exist")
void deleteApprovalWhenApprovalDoesNotExistReturnsNotFound() throws Exception {
doThrow(new NotFoundException("Approval not found")).when(approvalService).deleteApproval(approvalId);

mockMvc.perform(delete("/approvals/{approvalId}/v1", approvalId))
.andExpect(status().isNotFound());
}

private Approval createApproval() {
return Approval.builder()
.id(approvalId)
.subjectId(subjectId)
.subjectType(AuditSubjectType.COURT)
.userId(userId)
.build();
}

private ApprovalStatus createApprovalStatus() {
return new ApprovalStatus(
subjectId,
AuditSubjectType.COURT,
"Test Court",
true,
approvalId,
userId,
User.builder().id(userId).email("approver@justice.gov.uk").build(),
null
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package uk.gov.hmcts.reform.fact.data.api.controllers;

import uk.gov.hmcts.reform.fact.data.api.dto.ApprovalStatus;
import uk.gov.hmcts.reform.fact.data.api.entities.Approval;
import uk.gov.hmcts.reform.fact.data.api.security.SecuredFactRestController;
import uk.gov.hmcts.reform.fact.data.api.services.ApprovalService;
import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidUUID;

import java.util.List;
import java.util.UUID;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@SecuredFactRestController(
name = "Approval",
description = "Operations related to approvals",
preAuthorize = "@authService.isAdmin()"
)
@RequiredArgsConstructor
@RequestMapping("/approvals")
@SuppressWarnings("java:S4684")
public class ApprovalController {

private final ApprovalService approvalService;

@GetMapping("/v1")
@Operation(summary = "Get all approvals")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved approvals")
})
public ResponseEntity<List<ApprovalStatus>> getAllApprovals() {
return ResponseEntity.ok(approvalService.getAllApprovalStatuses());
}

@PostMapping("/v1")
@Operation(summary = "Create an approval")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully created approval"),
@ApiResponse(responseCode = "400", description = "Invalid request"),
@ApiResponse(responseCode = "404", description = "User or subject not found")
})
public ResponseEntity<Approval> createApproval(@Valid @RequestBody Approval approval) {
return ResponseEntity.status(HttpStatus.CREATED).body(approvalService.createApproval(approval));
}

@DeleteMapping("/{approvalId}/v1")
@Operation(summary = "Delete an approval by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Successfully deleted approval"),
@ApiResponse(responseCode = "400", description = "Invalid approval ID supplied"),
@ApiResponse(responseCode = "404", description = "Approval not found")
})
public ResponseEntity<Void> deleteApproval(
@Parameter(description = "UUID of the approval", required = true) @ValidUUID @PathVariable String approvalId) {
approvalService.deleteApproval(UUID.fromString(approvalId));
return ResponseEntity.noContent().build();
}
}
Loading
Loading