mirror of https://github.com/tasks/tasks
Fix snooze causing duplicate notifications
parent
97a3f074d0
commit
4c245edbb4
@ -1,108 +1,258 @@
|
|||||||
package com.todoroo.astrid.alarms
|
package com.todoroo.astrid.alarms
|
||||||
|
|
||||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import dagger.hilt.android.testing.UninstallModules
|
import dagger.hilt.android.testing.UninstallModules
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.tasks.data.entity.Alarm
|
import org.tasks.SuspendFreeze.Companion.freezeAt
|
||||||
import org.tasks.data.entity.Alarm.Companion.TYPE_DATE_TIME
|
import org.tasks.data.createDueDate
|
||||||
import org.tasks.data.entity.Alarm.Companion.TYPE_RANDOM
|
|
||||||
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE
|
|
||||||
import org.tasks.data.entity.Alarm.Companion.whenDue
|
|
||||||
import org.tasks.data.entity.Alarm.Companion.whenOverdue
|
|
||||||
import org.tasks.data.dao.AlarmDao
|
|
||||||
import org.tasks.data.dao.TaskDao
|
import org.tasks.data.dao.TaskDao
|
||||||
import org.tasks.date.DateTimeUtils.newDateTime
|
import org.tasks.data.entity.Alarm
|
||||||
|
import org.tasks.data.entity.Notification
|
||||||
|
import org.tasks.data.entity.Task
|
||||||
import org.tasks.injection.InjectingTestCase
|
import org.tasks.injection.InjectingTestCase
|
||||||
import org.tasks.injection.ProductionModule
|
import org.tasks.injection.ProductionModule
|
||||||
import org.tasks.jobs.AlarmEntry
|
|
||||||
import org.tasks.makers.TaskMaker.COMPLETION_TIME
|
|
||||||
import org.tasks.makers.TaskMaker.DELETION_TIME
|
|
||||||
import org.tasks.makers.TaskMaker.DUE_DATE
|
|
||||||
import org.tasks.makers.TaskMaker.DUE_TIME
|
|
||||||
import org.tasks.makers.TaskMaker.REMINDER_LAST
|
|
||||||
import org.tasks.makers.TaskMaker.newTask
|
|
||||||
import org.tasks.time.DateTime
|
import org.tasks.time.DateTime
|
||||||
|
import org.tasks.time.DateTimeUtils2
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@UninstallModules(ProductionModule::class)
|
@UninstallModules(ProductionModule::class)
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
class AlarmJobServiceTest : InjectingTestCase() {
|
class AlarmJobServiceTest : InjectingTestCase() {
|
||||||
@Inject lateinit var alarmDao: AlarmDao
|
|
||||||
@Inject lateinit var taskDao: TaskDao
|
@Inject lateinit var taskDao: TaskDao
|
||||||
@Inject lateinit var alarmService: AlarmService
|
@Inject lateinit var alarmService: AlarmService
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun scheduleAlarm() = runBlocking {
|
fun testNoAlarms() = runBlocking {
|
||||||
val task = taskDao.createNew(newTask())
|
testResults(emptyList(), 0)
|
||||||
val alarm = insertAlarm(Alarm(task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME))
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun futureAlarmWithNoPastAlarm() = runBlocking {
|
||||||
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
|
taskDao.insert(
|
||||||
|
Task(
|
||||||
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 18).millis
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(1, mutableSetOf(Alarm(0, 0, Alarm.TYPE_REL_END)))
|
||||||
|
|
||||||
verify(overdue = listOf(AlarmEntry(alarm, task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME)))
|
testResults(emptyList(), DateTime(2024, 5, 18, 18, 0).millis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun ignoreStaleAlarm() = runBlocking {
|
fun pastAlarmWithNoFutureAlarm() = runBlocking {
|
||||||
val alarmTime = DateTime(2017, 9, 24, 19, 57)
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
val task = taskDao.createNew(newTask(with(REMINDER_LAST, alarmTime.endOfMinute())))
|
taskDao.insert(
|
||||||
alarmDao.insert(Alarm(task, alarmTime.millis, TYPE_DATE_TIME))
|
Task(
|
||||||
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(1, mutableSetOf(Alarm(0, 0, Alarm.TYPE_REL_END)))
|
||||||
|
|
||||||
verify()
|
testResults(
|
||||||
|
listOf(
|
||||||
|
Notification(
|
||||||
|
taskId = 1L,
|
||||||
|
timestamp = DateTimeUtils2.currentTimeMillis(),
|
||||||
|
type = Alarm.TYPE_REL_END
|
||||||
|
)
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun dontScheduleReminderForCompletedTask() = runBlocking {
|
fun pastRecurringAlarmWithFutureRecurrence() = runBlocking {
|
||||||
val task = taskDao.insert(
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
newTask(
|
taskDao.insert(
|
||||||
with(DUE_DATE, newDateTime()),
|
Task(
|
||||||
with(COMPLETION_TIME, newDateTime())
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(
|
||||||
|
1,
|
||||||
|
mutableSetOf(
|
||||||
|
Alarm(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Alarm.TYPE_REL_END,
|
||||||
|
repeat = 1,
|
||||||
|
interval = TimeUnit.HOURS.toMillis(6)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
alarmDao.insert(whenDue(task))
|
|
||||||
|
|
||||||
verify()
|
testResults(
|
||||||
|
listOf(
|
||||||
|
Notification(
|
||||||
|
taskId = 1L,
|
||||||
|
timestamp = DateTimeUtils2.currentTimeMillis(),
|
||||||
|
type = Alarm.TYPE_REL_END
|
||||||
|
)
|
||||||
|
),
|
||||||
|
DateTime(2024, 5, 18, 0, 0).millis
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun dontScheduleReminderForDeletedTask() = runBlocking {
|
fun pastAlarmsRemoveSnoozed() = runBlocking {
|
||||||
val task = taskDao.insert(
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
newTask(
|
taskDao.insert(
|
||||||
with(DUE_DATE, newDateTime()),
|
Task(
|
||||||
with(DELETION_TIME, newDateTime())
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
alarmDao.insert(whenDue(task))
|
alarmService.synchronizeAlarms(
|
||||||
|
1,
|
||||||
|
mutableSetOf(
|
||||||
|
Alarm(0, 0, Alarm.TYPE_REL_END),
|
||||||
|
Alarm(0, DateTimeUtils2.currentTimeMillis(), Alarm.TYPE_SNOOZE)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
testResults(
|
||||||
|
listOf(
|
||||||
|
Notification(
|
||||||
|
taskId = 1L,
|
||||||
|
timestamp = DateTimeUtils2.currentTimeMillis(),
|
||||||
|
type = Alarm.TYPE_REL_END
|
||||||
|
)
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
verify()
|
assertEquals(
|
||||||
|
listOf(Alarm(id = 1, task = 1, time = 0, type = Alarm.TYPE_REL_END)),
|
||||||
|
alarmService.getAlarms(1)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun snoozeOverridesAll() = runBlocking {
|
fun futureSnoozeOverrideOverdue() = runBlocking {
|
||||||
val now = newDateTime()
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
val task = taskDao.insert(newTask(with(DUE_TIME, now)))
|
taskDao.insert(
|
||||||
|
Task(
|
||||||
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(
|
||||||
|
1,
|
||||||
|
mutableSetOf(
|
||||||
|
Alarm(0, 0, Alarm.TYPE_REL_END),
|
||||||
|
Alarm(
|
||||||
|
0,
|
||||||
|
DateTimeUtils2.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5),
|
||||||
|
Alarm.TYPE_SNOOZE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
testResults(
|
||||||
|
emptyList(),
|
||||||
|
DateTimeUtils2.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
alarmDao.insert(whenDue(task))
|
@Test
|
||||||
alarmDao.insert(whenOverdue(task))
|
fun ignoreStaleAlarm() = runBlocking {
|
||||||
alarmDao.insert(Alarm(task, DateUtilities.ONE_HOUR, TYPE_RANDOM))
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
val alarm = alarmDao.insert(Alarm(task, now.plusMonths(12).millis, TYPE_SNOOZE))
|
taskDao.insert(
|
||||||
|
Task(
|
||||||
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
),
|
||||||
|
reminderLast = DateTime(2024, 5, 17, 18, 0).millis,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(
|
||||||
|
1,
|
||||||
|
mutableSetOf(Alarm(0, 0, Alarm.TYPE_REL_END))
|
||||||
|
)
|
||||||
|
|
||||||
verify(future = listOf(AlarmEntry(alarm, task, now.plusMonths(12).millis, TYPE_SNOOZE)))
|
testResults(
|
||||||
|
emptyList(),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun insertAlarm(alarm: Alarm): Long {
|
@Test
|
||||||
alarm.id = alarmDao.insert(alarm)
|
fun dontScheduleForCompletedTask() = runBlocking {
|
||||||
return alarm.id
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
|
taskDao.insert(
|
||||||
|
Task(
|
||||||
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
),
|
||||||
|
completionDate = DateTime(2024, 5, 17, 14, 0).millis,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(
|
||||||
|
1,
|
||||||
|
mutableSetOf(Alarm(0, 0, Alarm.TYPE_REL_END))
|
||||||
|
)
|
||||||
|
|
||||||
|
testResults(
|
||||||
|
emptyList(),
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dontScheduleForDeletedTask() = runBlocking {
|
||||||
|
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
|
||||||
|
taskDao.insert(
|
||||||
|
Task(
|
||||||
|
dueDate = createDueDate(
|
||||||
|
Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
DateTime(2024, 5, 17).millis
|
||||||
|
),
|
||||||
|
deletionDate = DateTime(2024, 5, 17, 14, 0).millis,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
alarmService.synchronizeAlarms(
|
||||||
|
1,
|
||||||
|
mutableSetOf(Alarm(0, 0, Alarm.TYPE_REL_END))
|
||||||
|
)
|
||||||
|
|
||||||
private suspend fun verify(
|
testResults(
|
||||||
overdue: List<AlarmEntry> = emptyList(),
|
emptyList(),
|
||||||
future: List<AlarmEntry> = emptyList(),
|
0
|
||||||
) {
|
)
|
||||||
val (actualOverdue, actualFuture) = alarmService.getAlarms()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(overdue, actualOverdue)
|
private suspend fun testResults(notifications: List<Notification>, nextAlarm: Long) {
|
||||||
assertEquals(future, actualFuture)
|
val actualNextAlarm = alarmService.triggerAlarms {
|
||||||
|
assertEquals(notifications, it)
|
||||||
|
it.forEach { taskDao.setLastNotified(it.taskId, DateTimeUtils2.currentTimeMillis()) }
|
||||||
|
}
|
||||||
|
assertEquals(nextAlarm, actualNextAlarm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue