feat(data): the Room database, its four tables and their DAOs
alarms, timers, world_clocks and stopwatch_laps, with the schema exported to app/schemas and committed so migrations can be tested from v1 onwards. The DAOs return Flows for reads and keep every multi-step write inside a @Transaction, so a read-modify-write cannot lose a concurrent one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package de.jeanlucmakiola.clockula.data.db
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.data.alarms.AlarmEntity
|
||||
import de.jeanlucmakiola.clockula.data.worldclocks.WorldClockEntity
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* The real DAOs against an in-memory Room database — the half of the data layer
|
||||
* a fake cannot prove: generated SQL, unique indices and `@Transaction`.
|
||||
* These run on a device only; the build gate compiles them.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ClockulaDatabaseTest {
|
||||
|
||||
private lateinit var db: ClockulaDatabase
|
||||
|
||||
private fun alarm(hour: Int, enabled: Boolean) = AlarmEntity(
|
||||
hour = hour,
|
||||
minute = 30,
|
||||
label = "Gym",
|
||||
enabled = enabled,
|
||||
repeatDays = 31,
|
||||
skipNextOccurrence = false,
|
||||
ringtoneUri = "content://media/42",
|
||||
vibrate = true,
|
||||
snoozeMinutes = 7,
|
||||
snoozeLimit = 2,
|
||||
volumeRampSeconds = 30,
|
||||
dismissChallenge = "MATH",
|
||||
createdAt = 1_700_000_000_000L,
|
||||
updatedAt = 1_700_000_060_000L,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
db = Room.inMemoryDatabaseBuilder(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
ClockulaDatabase::class.java,
|
||||
).build()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anAlarmRoundTripsThroughTheRealDao() = runBlocking {
|
||||
val id = db.alarmDao().insert(alarm(hour = 7, enabled = true))
|
||||
|
||||
val stored = db.alarmDao().observeAll().first().single()
|
||||
|
||||
assertThat(stored).isEqualTo(alarm(hour = 7, enabled = true).copy(id = id))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enabledFiltersOnTheIndexedColumn() = runBlocking {
|
||||
val enabledId = db.alarmDao().insert(alarm(hour = 7, enabled = true))
|
||||
db.alarmDao().insert(alarm(hour = 8, enabled = false))
|
||||
|
||||
val enabled = db.alarmDao().enabled()
|
||||
|
||||
assertThat(enabled.map { it.id }).containsExactly(enabledId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theWorldClockZoneIdIsUnique() = runBlocking {
|
||||
db.worldClockDao().insert(WorldClockEntity(zoneId = "Europe/Berlin", label = null, sortOrder = 0))
|
||||
|
||||
val second = db.worldClockDao()
|
||||
.insert(WorldClockEntity(zoneId = "Europe/Berlin", label = null, sortOrder = 1))
|
||||
|
||||
assertThat(second).isEqualTo(-1L)
|
||||
assertThat(db.worldClockDao().observeAll().first()).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appendLapIncrementsTheIndexAndDerivesTheSplit() = runBlocking {
|
||||
val first = db.lapDao().appendLap(12_000L)
|
||||
val second = db.lapDao().appendLap(20_000L)
|
||||
|
||||
assertThat(listOf(first.lapIndex, second.lapIndex)).containsExactly(1, 2).inOrder()
|
||||
assertThat(listOf(first.splitMillis, second.splitMillis)).containsExactly(12_000L, 8_000L).inOrder()
|
||||
|
||||
db.lapDao().deleteAll()
|
||||
|
||||
assertThat(db.lapDao().observeAll().first()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reorderRewritesEveryRowsSortOrder() = runBlocking {
|
||||
val ids = listOf("Europe/Berlin", "America/New_York", "Pacific/Auckland").mapIndexed { index, zone ->
|
||||
db.worldClockDao().insert(WorldClockEntity(zoneId = zone, label = null, sortOrder = index))
|
||||
}
|
||||
|
||||
db.worldClockDao().reorder(ids.reversed())
|
||||
|
||||
assertThat(db.worldClockDao().observeAll().first().map { it.id })
|
||||
.containsExactlyElementsIn(ids.reversed())
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theDatabaseOpensAtVersionOne() {
|
||||
assertThat(db.openHelper.readableDatabase.version).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user