From 83ac1cf3fb5e67552ffe4f283d8eb051ecb698a5 Mon Sep 17 00:00:00 2001 From: Henrik van Peet <72788863+henneboy@users.noreply.github.com> Date: Thu, 9 May 2024 13:23:31 +0200 Subject: [PATCH] Validation for ensuring a task can not be scheduled before current time (#169) * Validation for ensuring a task can be scheduled before current time * Test for taskForm current time restriction --- .../components/DateRangePicker.kt | 3 +- .../schedulingfrontend/model/TaskForm.kt | 11 +++- .../schedulingfrontend/TaskFormTest.kt | 57 +++++++++++++++++-- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/components/DateRangePicker.kt b/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/components/DateRangePicker.kt index 2d2e6ceb..57e00772 100644 --- a/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/components/DateRangePicker.kt +++ b/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/components/DateRangePicker.kt @@ -31,6 +31,7 @@ import dk.scheduling.schedulingfrontend.model.Status import dk.scheduling.schedulingfrontend.ui.theme.SchedulingFrontendTheme import java.time.Instant import java.time.LocalDateTime +import java.time.LocalTime import java.time.ZoneId @OptIn(ExperimentalMaterial3Api::class) @@ -113,7 +114,7 @@ private fun DialogActions( data class DateRange(val startTime: Long?, val endTime: Long?) { private fun millisToLocalDateTime(millis: Long): LocalDateTime = - LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault()) + LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault()).with(LocalTime.MIDNIGHT) fun rangeStart(): LocalDateTime? = startTime?.let { millisToLocalDateTime(it) } diff --git a/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/model/TaskForm.kt b/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/model/TaskForm.kt index 3bd9b85a..39de7156 100644 --- a/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/model/TaskForm.kt +++ b/frontend/app/src/main/java/dk/scheduling/schedulingfrontend/model/TaskForm.kt @@ -41,10 +41,15 @@ data class TaskForm( val start = startDateTime() val end = endDateTime() val intervalLengthInMinutes = start.until(end, ChronoUnit.MINUTES) - return if (start.isBefore(end) && intervalLengthInMinutes >= duration.value.toLong()) { - Status(true, "") - } else { + val latestPossibleEventStartTime = end.minus(duration.value.toLong(), ChronoUnit.MINUTES) + return if (!start.isBefore(end)) { Status(false, "Start time must be before end time, change times or date interval") + } else if (intervalLengthInMinutes < duration.value.toLong()) { + Status(false, "The duration may not be larger than the specified interval") + } else if (LocalDateTime.now().isAfter(latestPossibleEventStartTime)) { + Status(false, "There is not enough time perform the task before the deadline") + } else { + Status(true, "") } } diff --git a/frontend/app/src/test/java/dk/scheduling/schedulingfrontend/TaskFormTest.kt b/frontend/app/src/test/java/dk/scheduling/schedulingfrontend/TaskFormTest.kt index 96458b56..26000d5d 100644 --- a/frontend/app/src/test/java/dk/scheduling/schedulingfrontend/TaskFormTest.kt +++ b/frontend/app/src/test/java/dk/scheduling/schedulingfrontend/TaskFormTest.kt @@ -6,11 +6,16 @@ import dk.scheduling.schedulingfrontend.components.DateRange import dk.scheduling.schedulingfrontend.model.Duration import dk.scheduling.schedulingfrontend.model.TaskForm import org.junit.Test +import java.time.Instant +import java.time.LocalDateTime @OptIn(ExperimentalMaterial3Api::class) class TaskFormTest { private val timeMidday = TimePickerState(12, 0, true) private val timeOneMinutePastMidday = TimePickerState(12, 1, true) + private val millisecondsInADay = 86400000L + private val timeSinceEpochInMillis = Instant.now().toEpochMilli() + private val tomorrowInMillis = timeSinceEpochInMillis + millisecondsInADay @Test fun statusIsValidTest() { @@ -18,7 +23,7 @@ class TaskFormTest { TaskForm( 1, Duration("1"), - DateRange(0, 0), + DateRange(tomorrowInMillis, tomorrowInMillis), timeMidday, timeOneMinutePastMidday, ) @@ -28,11 +33,29 @@ class TaskFormTest { TaskForm( 1, Duration("1"), - DateRange(0, Long.MAX_VALUE), + DateRange(timeSinceEpochInMillis, tomorrowInMillis), timeMidday, timeMidday, ) assert(taskDifferentDates.status().isValid) { "Valid task, different dates" } + + // Say the current time is 14:10 + // Then the start time is 14:00 and end time is 14:20 + // The duration of the task is 7 minutes, which fits the interval. + val timeLocal = LocalDateTime.now().minusMinutes(10) + val timeLocalEnd = timeLocal.plusMinutes(20) + val taskTimeNowCloseToEndTime = + TaskForm( + 1, + Duration("7"), + DateRange(tomorrowInMillis, tomorrowInMillis), + TimePickerState(timeLocal.hour, timeLocal.minute, true), + TimePickerState(timeLocalEnd.hour, timeLocalEnd.minute, true), + ) + // If the task is scheduled instantly, then the event could start at 14:10. + // This would leave enough time to perform the task before the end time: + // 14:10 (current time) + 0:07 (the duration) < 14:20 (end time) + assert(taskTimeNowCloseToEndTime.status().isValid) { "Valid task, end time is after (current time + duration)" } } @Test @@ -41,7 +64,7 @@ class TaskFormTest { TaskForm( 1, Duration("2"), - DateRange(0, 0), + DateRange(tomorrowInMillis, tomorrowInMillis), timeMidday, timeOneMinutePastMidday, ) @@ -52,8 +75,7 @@ class TaskFormTest { 1, // 24 hours and one minute in minutes Duration("1441"), - // One day in minutes - DateRange(0, 86400000), + DateRange(timeSinceEpochInMillis, tomorrowInMillis), timeMidday, timeMidday, ) @@ -66,10 +88,33 @@ class TaskFormTest { TaskForm( null, Duration("1"), - DateRange(0, 0), + DateRange(tomorrowInMillis, tomorrowInMillis), timeMidday, timeOneMinutePastMidday, ) assert(!taskSameDate.status().isValid) { "Invalid task, deviceId is null (unselected)" } } + + @Test + fun endTime_before_currentTimePlusDuration_InvalidStatus() { + // Say the current time is 14:04 + // Then the start time is 14:00 and end time is 14:06 + // The duration of the task is 4 minutes, which fits the interval. + val timeLocal = LocalDateTime.now().minusMinutes(4) + val timeLocalEnd = timeLocal.plusMinutes(6) + + val timeNowCloseToEndTime = + TaskForm( + 1, + Duration("4"), + DateRange(timeSinceEpochInMillis, timeSinceEpochInMillis), + TimePickerState(timeLocal.hour, timeLocal.minute, true), + TimePickerState(timeLocalEnd.hour, timeLocalEnd.minute, true), + ) + // The current time is 14:04, and even if the task is instantly scheduled and started/executed, + // then the event would be 14:04. + // But this does not leave enough time to run the 4 minute duration before the end time 14:06 + // 14:04 (current time) + 0:04 (the duration) > 14:06 (end time) -> Impossible! + assert(!timeNowCloseToEndTime.status().isValid) { "Invalid task, end time is before (current time + duration)" } + } }