data: add ColumnReader.toEventInstance() with defensive validation (§8)
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package de.jeanlucmakiola.calendula.data.calendar
|
||||
|
||||
import android.util.Log
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
|
||||
private const val TAG = "InstanceMapper"
|
||||
|
||||
internal fun ColumnReader.toEventInstance(): EventInstance? {
|
||||
val begin = getLong(InstanceProjection.IDX_BEGIN)
|
||||
val end = getLong(InstanceProjection.IDX_END)
|
||||
|
||||
if (begin < 0L) {
|
||||
Log.w(TAG, "Dropping row with negative begin=$begin")
|
||||
return null
|
||||
}
|
||||
if (end < begin) {
|
||||
Log.w(TAG, "Dropping row with end=$end < begin=$begin")
|
||||
return null
|
||||
}
|
||||
|
||||
val rawTitle = getString(InstanceProjection.IDX_TITLE)
|
||||
val title = if (rawTitle.isNullOrEmpty()) Fallbacks.UNTITLED_EVENT else rawTitle
|
||||
|
||||
val color = if (isNull(InstanceProjection.IDX_EVENT_COLOR)) {
|
||||
getInt(InstanceProjection.IDX_CALENDAR_COLOR)
|
||||
} else {
|
||||
getInt(InstanceProjection.IDX_EVENT_COLOR)
|
||||
}
|
||||
|
||||
return EventInstance(
|
||||
instanceId = getLong(InstanceProjection.IDX_INSTANCE_ID),
|
||||
eventId = getLong(InstanceProjection.IDX_EVENT_ID),
|
||||
calendarId = getLong(InstanceProjection.IDX_CALENDAR_ID),
|
||||
title = title,
|
||||
start = begin.toKotlinInstantFromEpochMillis(),
|
||||
end = end.toKotlinInstantFromEpochMillis(),
|
||||
isAllDay = getInt(InstanceProjection.IDX_ALL_DAY) != 0,
|
||||
color = color,
|
||||
location = getString(InstanceProjection.IDX_LOCATION),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package de.jeanlucmakiola.calendula.data.calendar
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.time.Instant
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class InstanceMapperTest {
|
||||
|
||||
private fun reader(
|
||||
instanceId: Long = 10L,
|
||||
eventId: Long = 1L,
|
||||
calendarId: Long = 1L,
|
||||
title: String? = "Meet",
|
||||
begin: Long = 1_000_000_000L,
|
||||
end: Long = 1_000_003_600L,
|
||||
allDay: Int = 0,
|
||||
eventColor: Any? = null,
|
||||
calendarColor: Int = 0xFFAABBCC.toInt(),
|
||||
location: String? = null,
|
||||
): MapColumnReader = MapColumnReader(
|
||||
InstanceProjection.IDX_INSTANCE_ID to instanceId,
|
||||
InstanceProjection.IDX_EVENT_ID to eventId,
|
||||
InstanceProjection.IDX_CALENDAR_ID to calendarId,
|
||||
InstanceProjection.IDX_TITLE to title,
|
||||
InstanceProjection.IDX_BEGIN to begin,
|
||||
InstanceProjection.IDX_END to end,
|
||||
InstanceProjection.IDX_ALL_DAY to allDay,
|
||||
InstanceProjection.IDX_EVENT_COLOR to eventColor,
|
||||
InstanceProjection.IDX_CALENDAR_COLOR to calendarColor,
|
||||
InstanceProjection.IDX_LOCATION to location,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `happy path - non-allday event`() {
|
||||
val inst = reader().toEventInstance()
|
||||
assertThat(inst).isNotNull()
|
||||
assertThat(inst!!.title).isEqualTo("Meet")
|
||||
assertThat(inst.isAllDay).isFalse()
|
||||
assertThat(inst.start).isEqualTo(Instant.fromEpochMilliseconds(1_000_000_000L))
|
||||
assertThat(inst.end).isEqualTo(Instant.fromEpochMilliseconds(1_000_003_600L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `event color falls back to calendar color when null`() {
|
||||
val inst = reader(eventColor = null, calendarColor = 0xFF112233.toInt()).toEventInstance()
|
||||
assertThat(inst!!.color).isEqualTo(0xFF112233.toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `event color wins over calendar color when present`() {
|
||||
val inst = reader(
|
||||
eventColor = 0xFFDEADBE.toInt(),
|
||||
calendarColor = 0xFF112233.toInt(),
|
||||
).toEventInstance()
|
||||
assertThat(inst!!.color).isEqualTo(0xFFDEADBE.toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `null title falls back to placeholder`() {
|
||||
val inst = reader(title = null).toEventInstance()
|
||||
assertThat(inst!!.title).isEqualTo(Fallbacks.UNTITLED_EVENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty title falls back to placeholder`() {
|
||||
val inst = reader(title = "").toEventInstance()
|
||||
assertThat(inst!!.title).isEqualTo(Fallbacks.UNTITLED_EVENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dtend before dtstart drops the row`() {
|
||||
val inst = reader(begin = 2000L, end = 1000L).toEventInstance()
|
||||
assertThat(inst).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dtstart before unix epoch drops the row`() {
|
||||
val inst = reader(begin = -1L, end = 1000L).toEventInstance()
|
||||
assertThat(inst).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `all-day flag 1 maps to true`() {
|
||||
val inst = reader(allDay = 1).toEventInstance()
|
||||
assertThat(inst!!.isAllDay).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `location passes through when present`() {
|
||||
val inst = reader(location = "Berlin").toEventInstance()
|
||||
assertThat(inst!!.location).isEqualTo("Berlin")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user