Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
Expand Up @@ -248,7 +248,6 @@ public ResponseEntity<Void> undeleteBooking(@PathVariable UUID bookingId) {
return ok().build();
}


private void validateRequestWithBody(UUID bookingId, CreateBookingDTO createBookingDTO) {
if (!bookingId.equals(createBookingDTO.getId())) {
throw new PathPayloadMismatchException("bookingId", "bookingDTO.id");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
import uk.gov.hmcts.reform.preapi.dto.validators.BookingScheduledForNotPastOrNotChangedConstraint;
import uk.gov.hmcts.reform.preapi.dto.validators.ParticipantTypeConstraint;
import uk.gov.hmcts.reform.preapi.entities.Booking;

Expand All @@ -20,7 +19,6 @@
@NoArgsConstructor
@Schema(description = "CreateBookingDTO")
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@BookingScheduledForNotPastOrNotChangedConstraint(message = "scheduled_for is required and must not be before today")
public class CreateBookingDTO {
@Schema(description = "CreateBookingId")
@NotNull(message = "id is required")
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ public boolean hasUpsertAccess(UserAuthentication authentication, CreateRecordin

public boolean hasUpsertAccess(UserAuthentication authentication, CreateShareBookingDTO dto) {
return authentication.getUserId().equals(dto.getSharedByUser())
&& (authentication.isAdmin() || hasBookingAccess(authentication, dto.getBookingId()));
&& (authentication.isAdmin() || hasBookingAccess(authentication, dto.getBookingId()))
|| authentication.hasRole("ROLE_SUPER_USER");
}

public boolean hasUpsertAccess(UserAuthentication authentication, CreateCaseDTO dto) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import uk.gov.hmcts.reform.preapi.enums.CaseState;
import uk.gov.hmcts.reform.preapi.enums.RecordingStatus;
import uk.gov.hmcts.reform.preapi.enums.UpsertResult;
import uk.gov.hmcts.reform.preapi.exception.BadRequestException;
import uk.gov.hmcts.reform.preapi.exception.NotFoundException;
import uk.gov.hmcts.reform.preapi.exception.ResourceInDeletedStateException;
import uk.gov.hmcts.reform.preapi.exception.ResourceInWrongStateException;
Expand All @@ -30,6 +31,8 @@
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -126,6 +129,16 @@ public Page<BookingDTO> searchBy(
@Transactional
@PreAuthorize("@authorisationService.hasUpsertAccess(authentication, #createBookingDTO)")
public UpsertResult upsert(CreateBookingDTO createBookingDTO) {
var auth = ((UserAuthentication) SecurityContextHolder.getContext().getAuthentication());

var localDateField = LocalDateTime.ofInstant(createBookingDTO.getScheduledFor().toInstant(),
ZoneId.of("Europe/London")).toLocalDate();
var today = LocalDate.now();

if (localDateField.isBefore(today)
Copy link
Contributor

@lydiaralphgov lydiaralphgov Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this adds a restriction to the API that wasn't there before, i.e. it adds a guard to make sure bookings are in the future. That's fine, but we need to create a separate ticket for this piece of code and put it in front of Jacob to make sure it's been signed off by service as its a behavioural change. edit: sorry I should have read the whole PR first!

I agree we need the Super User exception to allow us to recover cases.

&& !auth.hasRole("ROLE_SUPER_USER")) {
throw new BadRequestException("Scheduled date must not be in the past");
}

if (bookingAlreadyDeleted(createBookingDTO.getId())) {
throw new ResourceInDeletedStateException("BookingDTO", createBookingDTO.getId().toString());
Expand All @@ -137,7 +150,7 @@ public UpsertResult upsert(CreateBookingDTO createBookingDTO) {
var caseEntity = caseRepository.findByIdAndDeletedAtIsNull(createBookingDTO.getCaseId())
.orElseThrow(() -> new NotFoundException("Case: " + createBookingDTO.getCaseId()));

if (caseEntity.getState() != CaseState.OPEN) {
if (caseEntity.getState() != CaseState.OPEN && !auth.hasRole("ROLE_SUPER_USER")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that we should have this exception, as I think we need to avoid modifying closed cases altogether

throw new ResourceInWrongStateException(
"Booking",
createBookingDTO.getId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,15 @@ public UpsertResult upsert(CreateCaseDTO createCaseDTO) {
var isCaseClosureCancellation = false;
var isCasePendingClosure = false;

var auth = ((UserAuthentication) SecurityContextHolder.getContext().getAuthentication());

if (isUpdate) {
if (foundCase.get().isDeleted()) {
throw new ResourceInDeletedStateException("CaseDTO", createCaseDTO.getId().toString());
}
if (foundCase.get().getState() != CaseState.OPEN
&& foundCase.get().getState() == createCaseDTO.getState()
&& !auth.hasRole("ROLE_SUPER_USER")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto - I don't like this superpower

) {
throw new ResourceInWrongStateException(
"Resource Case("
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,62 +413,6 @@ void createBookingEndpoint400ScheduledForMissing() throws Exception {
.isEqualTo("{\"scheduledFor\":\"scheduled_for is required and must not be before today\"}");
}

@DisplayName("Should fail to create a booking with 400 response code as scheduledFor is before today")
@Test
void createBookingEndpoint400ScheduledForInThePast() throws Exception {

var caseId = UUID.randomUUID();
var bookingId = UUID.randomUUID();
var booking = new CreateBookingDTO();
booking.setId(bookingId);
booking.setCaseId(caseId);
booking.setParticipants(getCreateParticipantDTOs());
booking.setScheduledFor(Timestamp.from(OffsetDateTime.now().minusWeeks(1).toInstant()));

CaseDTO mockCaseDTO = new CaseDTO();
when(caseService.findById(caseId)).thenReturn(mockCaseDTO);

MvcResult response = mockMvc.perform(put(getPath(bookingId))
.with(csrf())
.content(OBJECT_MAPPER.writeValueAsString(booking))
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isBadRequest())
.andReturn();

assertThat(response.getResponse().getContentAsString())
.isEqualTo(
"{\"scheduledFor\":\"must not be before today\"}"
);
}

@DisplayName("Should fail to create a booking with 400 response code as scheduledFor is in the past same day")
@Test
void createBookingEndpointScheduledForInThePastButToday() throws Exception {

var caseId = UUID.randomUUID();
var bookingId = UUID.randomUUID();
var booking = new CreateBookingDTO();
booking.setId(bookingId);
booking.setCaseId(caseId);
booking.setParticipants(getCreateParticipantDTOs());
booking.setScheduledFor(
Timestamp.from(Instant.now().truncatedTo(ChronoUnit.DAYS))
);
booking.setParticipants(getCreateParticipantDTOs());

CaseDTO mockCaseDTO = new CaseDTO();
when(caseService.findById(caseId)).thenReturn(mockCaseDTO);
when(bookingService.upsert(any(CreateBookingDTO.class))).thenReturn(UpsertResult.CREATED);

mockMvc.perform(put(getPath(bookingId))
.with(csrf())
.content(OBJECT_MAPPER.writeValueAsString(booking))
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isCreated());
}

@DisplayName("Should fail to update a booking with 400 response code as its already deleted")
@Test
void updateBookingEndpoint400() throws Exception {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,21 @@ void hasUpsertAccessUserIsNotSharing() {
assertFalse(authorisationService.hasUpsertAccess(authenticationUser, dto));
}

@DisplayName("Should grant upsert access when the authenticated super user is not the one sharing the booking,")
@Test
void hasUpsertAccessSuperUserIsNotSharing() {
var dto = new CreateShareBookingDTO();

dto.setSharedByUser(UUID.randomUUID());
dto.setBookingId(UUID.randomUUID());

when(authenticationUser.getUserId()).thenReturn(UUID.randomUUID());
when(authenticationUser.isAdmin()).thenReturn(false);
when(authenticationUser.hasRole("ROLE_SUPER_USER")).thenReturn(true);

assertTrue(authorisationService.hasUpsertAccess(authenticationUser, dto));
}

@DisplayName("Should grant upsert access when the authenticated user is an admin")
@Test
void hasUpsertAccessUserIsAdmin() {
Expand Down
Loading