Skip to content
Open
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
Expand Up @@ -116,7 +116,12 @@ public QueueEntry transitionQueueEntry(QueueEntryTransition queueEntryTransition
if (currentState.getEndedAt() != null) {
throw new IllegalStateException("Cannot transition a queue entry that has already ended");
}

// Validate transition date is not in the future (with 1-minute tolerance for clock drift)
Date maxAllowedDate = new Date(System.currentTimeMillis() + 60000L);
if (queueEntryTransition.getTransitionDate() != null
&& queueEntryTransition.getTransitionDate().after(maxAllowedDate)) {
throw new APIException("Transition date cannot be in the future");
}
// Capture the dateChanged for optimistic locking
Date expectedDateChanged = currentState.getDateChanged();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.openmrs.User;
import org.openmrs.Visit;
import org.openmrs.VisitAttributeType;
import org.openmrs.api.APIException;
import org.openmrs.api.VisitService;
import org.openmrs.api.context.Context;
import org.openmrs.api.context.UserContext;
Expand Down Expand Up @@ -470,4 +471,47 @@ public void shouldGenerateVisitQueueNumber() {
assertThat(queueNumber, notNullValue());
assertThat(queueNumber, equalTo("CON-053"));
}

@Test(expected = APIException.class)
public void shouldThrowWhenTransitionDateIsInTheFuture() {
QueueEntry queueEntry = new QueueEntry();
queueEntry.setQueueEntryId(1);
when(dao.get(1)).thenReturn(Optional.of(queueEntry));

QueueEntryTransition transition = new QueueEntryTransition();
transition.setQueueEntryToTransition(queueEntry);
// Set transition date 2 minutes in the future — well beyond the 1-minute tolerance
transition.setTransitionDate(DateUtils.addMinutes(new Date(), 2));

queueEntryService.transitionQueueEntry(transition);
}

@Test
public void shouldAllowTransitionDateWithinOneMinuteClockDriftTolerance() {
QueueEntry queueEntry = new QueueEntry();
queueEntry.setQueueEntryId(1);
queueEntry.setQueue(new Queue());
queueEntry.setPatient(new Patient());
queueEntry.setStatus(new Concept());
queueEntry.setPriority(new Concept());
queueEntry.setStartedAt(DateUtils.addHours(new Date(), -1));
when(dao.get(1)).thenReturn(Optional.of(queueEntry));
when(dao.updateIfUnmodified(any(), any())).thenReturn(true);
when(dao.createOrUpdate(any())).thenAnswer(invocation -> {
QueueEntry entry = invocation.getArgument(0);
if (entry.getId() == null) {
entry.setQueueEntryId(2);
}
return entry;
});

QueueEntryTransition transition = new QueueEntryTransition();
transition.setQueueEntryToTransition(queueEntry);
// Set transition date 30 seconds in future — within the 1-minute tolerance
transition.setTransitionDate(DateUtils.addMinutes(new Date(), 1));

// Should NOT throw — 30 seconds is within the allowed clock drift window
QueueEntry result = queueEntryService.transitionQueueEntry(transition);
assertThat(result, notNullValue());
}
}