diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt index 3434c4b92a59aa021ebd9c10f7269c716608845b..0a208c73332cfb9f026c11e5044228406b655d25 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.eventregistration.storage import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import okio.ByteString.Companion.toByteString import org.joda.time.Instant object CheckInDatabaseData { @@ -17,7 +18,7 @@ object CheckInDatabaseData { traceLocationEnd = Instant.parse("2021-01-01T15:00:00.000Z"), defaultCheckInLengthInMinutes = 15, traceLocationBytesBase64 = "", - signatureBase64 = "Signature", + signatureBase64 = "Signature".toByteArray().toByteString().base64(), checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"), checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"), completed = false, @@ -35,7 +36,7 @@ object CheckInDatabaseData { traceLocationEnd = null, defaultCheckInLengthInMinutes = null, traceLocationBytesBase64 = "", - signatureBase64 = "Signature", + signatureBase64 = "Signature".toByteArray().toByteString().base64(), checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"), checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"), completed = false, diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt index 60223bea5e51e2caba73607eb59fa01dabca3b01..b98dafcd84700ec2df653da9586fc160b72b78da 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt @@ -4,7 +4,10 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import de.rki.coronawarnapp.eventregistration.storage.CheckInDatabaseData.testCheckIn import de.rki.coronawarnapp.eventregistration.storage.CheckInDatabaseData.testCheckInWithoutCheckOutTime +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.mockk.mockk import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.joda.time.Instant @@ -82,4 +85,42 @@ class TraceLocationCheckInDaoTest : BaseTestInstrumentation() { checkInsFlow.first() shouldBe emptyList() } + + @Test + fun traceLocationCheckInDaoRetrieveById() = runBlocking { + val generatedId1 = checkInDao.insert(testCheckIn) + val generatedId2 = checkInDao.insert(testCheckIn) + + checkInDao.entryForId(generatedId1)!!.id shouldBe generatedId1 + checkInDao.entryForId(generatedId2)!!.id shouldBe generatedId2 + } + + @Test + fun traceLocationCheckInDaoDeleteById() = runBlocking { + val generatedId1 = checkInDao.insert(testCheckIn) + val generatedId2 = checkInDao.insert(testCheckIn) + + checkInDao.deleteByIds(listOf(generatedId1)) + checkInDao.entryForId(generatedId1) shouldBe null + checkInDao.entryForId(generatedId2) shouldNotBe null + } + + @Test + fun traceLocationCheckInDaoUpdateById() = runBlocking { + val generatedId1 = checkInDao.insert(testCheckIn) + + checkInDao.updateEntityById(generatedId1) { + it.copy(address = "test") + } + checkInDao.entryForId(generatedId1)!!.address shouldBe "test" + } + + @Test + fun traceLocationCheckInDaoUpdateById_raceCondition1(): Unit = runBlocking { + shouldThrow<IllegalStateException> { + checkInDao.updateEntityById(123) { + mockk() + } + } + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt index 0c3b0f922f67193953e2b5341eabe4562ad8fb11..373561208c44432afa1d60cef6d004665a5e8772 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.eventregistration.checkins +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity import okio.ByteString import org.joda.time.Instant @@ -22,3 +23,22 @@ data class CheckIn( val completed: Boolean, val createJournalEntry: Boolean ) + +fun CheckIn.toEntity() = TraceLocationCheckInEntity( + id = id, + guid = guid, + guidHashBase64 = guidHash.base64(), + version = version, + type = type, + description = description, + address = address, + traceLocationStart = traceLocationStart, + traceLocationEnd = traceLocationEnd, + defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes, + traceLocationBytesBase64 = traceLocationBytes.base64(), + signatureBase64 = signature.base64(), + checkInStart = checkInStart, + checkInEnd = checkInEnd, + completed = completed, + createJournalEntry = createJournalEntry +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt index 78e8305820c7c805182367627cb39576fd898345..e4e7c5618f09ede87d3696bd2fb146f15e5c1011 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt @@ -2,18 +2,16 @@ package de.rki.coronawarnapp.eventregistration.checkins import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao -import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity -import de.rki.coronawarnapp.util.coroutine.AppScope -import kotlinx.coroutines.CoroutineScope +import de.rki.coronawarnapp.eventregistration.storage.entity.toCheckIn +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import okio.ByteString.Companion.decodeBase64 +import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject class CheckInRepository @Inject constructor( - traceLocationDatabaseFactory: TraceLocationDatabase.Factory, - @AppScope private val appScope: CoroutineScope + traceLocationDatabaseFactory: TraceLocationDatabase.Factory ) { private val traceLocationDatabase: TraceLocationDatabase by lazy { @@ -29,59 +27,25 @@ class CheckInRepository @Inject constructor( .allEntries() .map { list -> list.map { it.toCheckIn() } } - fun addCheckIn(checkIn: CheckIn) { - appScope.launch { - checkInDao.insert(checkIn.toEntity()) - } - } + suspend fun addCheckIn(checkIn: CheckIn) = withContext(NonCancellable) { + Timber.d("addCheckIn(checkIn=%s)", checkIn) + if (checkIn.id == 0L) throw IllegalArgumentException("ID will be set by DB, ID should be 0!") - fun updateCheckIn(checkIn: CheckIn) { - appScope.launch { - checkInDao.update(checkIn.toEntity()) - } + checkInDao.insert(checkIn.toEntity()) } - fun clear() { - appScope.launch { - checkInDao.deleteAll() - } + suspend fun updateCheckIn(checkInId: Long, update: (CheckIn) -> CheckIn) = withContext(NonCancellable) { + Timber.d("updateCheckIn(checkInId=%d, update=%s)", checkInId, update) + checkInDao.updateEntityById(checkInId, update) } -} -private fun TraceLocationCheckInEntity.toCheckIn() = CheckIn( - id = id, - guid = guid, - guidHash = guidHashBase64.decodeBase64()!!, - version = version, - type = type, - description = description, - address = address, - traceLocationStart = traceLocationStart, - traceLocationEnd = traceLocationEnd, - defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes, - traceLocationBytes = traceLocationBytesBase64.decodeBase64()!!, - signature = signatureBase64.decodeBase64()!!, - checkInStart = checkInStart, - checkInEnd = checkInEnd, - completed = completed, - createJournalEntry = createJournalEntry -) + suspend fun deleteCheckIns(checkIns: Collection<CheckIn>) = withContext(NonCancellable) { + Timber.d("deleteCheckIns(checkIns=%s)", checkIns) + checkInDao.deleteByIds(checkIns.map { it.id }) + } -private fun CheckIn.toEntity() = TraceLocationCheckInEntity( - id = id, - guid = guid, - guidHashBase64 = guidHash.base64(), - version = version, - type = type, - description = description, - address = address, - traceLocationStart = traceLocationStart, - traceLocationEnd = traceLocationEnd, - defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes, - traceLocationBytesBase64 = traceLocationBytes.base64(), - signatureBase64 = signature.base64(), - checkInStart = checkInStart, - checkInEnd = checkInEnd, - completed = completed, - createJournalEntry = createJournalEntry -) + suspend fun clear() = withContext(NonCancellable) { + Timber.d("clear()") + checkInDao.deleteAll() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt index 75f5cbd8987f0d8172c5217cc03154afe0b02984..b45d75d1b09df3dd21f4967411f32e9e4f8c7ef4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt @@ -3,8 +3,12 @@ package de.rki.coronawarnapp.eventregistration.storage.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import androidx.room.Transaction import androidx.room.Update +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import de.rki.coronawarnapp.eventregistration.checkins.toEntity import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity +import de.rki.coronawarnapp.eventregistration.storage.entity.toCheckIn import kotlinx.coroutines.flow.Flow @Dao @@ -13,12 +17,29 @@ interface CheckInDao { @Query("SELECT * FROM checkin") fun allEntries(): Flow<List<TraceLocationCheckInEntity>> + @Query("SELECT * FROM checkin WHERE id = :id") + suspend fun entryForId(id: Long): TraceLocationCheckInEntity? + @Insert suspend fun insert(entity: TraceLocationCheckInEntity): Long @Update suspend fun update(entity: TraceLocationCheckInEntity) + @Transaction + suspend fun updateEntityById(checkInId: Long, update: (CheckIn) -> CheckIn) { + val current = entryForId(checkInId) ?: throw IllegalStateException("Entity $checkInId no longer exists.") + + val updated = update(current.toCheckIn()).also { + if (it.id != checkInId) throw UnsupportedOperationException("Can't change entity id: $it") + }.toEntity() + + update(updated) + } + @Query("DELETE FROM checkin") suspend fun deleteAll() + + @Query("DELETE FROM checkin WHERE id in (:idList)") + suspend fun deleteByIds(idList: List<Long>) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt index 493fd68ae7bd23eca7beac95203e7467e02ef912..ab868da7e00bb20b3959be5d47c7d2f85fdc4004 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt @@ -3,6 +3,8 @@ package de.rki.coronawarnapp.eventregistration.storage.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Instant @Entity(tableName = "checkin") @@ -24,3 +26,22 @@ data class TraceLocationCheckInEntity( @ColumnInfo(name = "completed") val completed: Boolean, @ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean ) + +fun TraceLocationCheckInEntity.toCheckIn() = CheckIn( + id = id, + guid = guid, + guidHash = guidHashBase64.decodeBase64()!!, + version = version, + type = type, + description = description, + address = address, + traceLocationStart = traceLocationStart, + traceLocationEnd = traceLocationEnd, + defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes, + traceLocationBytes = traceLocationBytesBase64.decodeBase64()!!, + signature = signatureBase64.decodeBase64()!!, + checkInStart = checkInStart, + checkInEnd = checkInEnd, + completed = completed, + createJournalEntry = createJournalEntry +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..8a775e5622bc1ddb7f2fa7710bb3629eb47795d6 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt @@ -0,0 +1,13 @@ +package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins + +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationVerifyResult + +sealed class CheckInEvent { + + data class ConfirmRemoveItem(val checkIn: CheckIn) : CheckInEvent() + + object ConfirmRemoveAll : CheckInEvent() + + data class ConfirmCheckIn(val result: TraceLocationVerifyResult) : CheckInEvent() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt index aadf44c7c54b4124555c852377e8de1c71d3ea7b..18273cb8a0965ff325d7b8ce98c34d6cc49e19d0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt @@ -4,6 +4,7 @@ import android.net.Uri import android.os.Bundle import android.view.View import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.core.net.toUri import androidx.core.view.isGone @@ -16,6 +17,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator import com.google.android.material.transition.Hold import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsFragmentBinding +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator @@ -87,13 +89,37 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag } } - viewModel.confirmationEvent.observe2(this) { - doNavigate( - CheckInsFragmentDirections.actionCheckInsFragmentToConfirmCheckInFragment(it.verifiedTraceLocation) - ) + viewModel.events.observe2(this) { + when (it) { + is CheckInEvent.ConfirmCheckIn -> { + doNavigate( + CheckInsFragmentDirections.actionCheckInsFragmentToConfirmCheckInFragment( + it.result.verifiedTraceLocation + ) + ) + } + is CheckInEvent.ConfirmRemoveItem -> { + showRemovalConfirmation(it.checkIn) + } + is CheckInEvent.ConfirmRemoveAll -> { + showRemovalConfirmation(null) + } + } } } + private fun showRemovalConfirmation(checkIn: CheckIn?) = AlertDialog.Builder(requireContext()).apply { + setTitle( + if (checkIn == null) R.string.trace_location_checkins_remove_all_title + else R.string.trace_location_checkins_remove_single_title + ) + setMessage(R.string.trace_location_checkins_remove_message) + setPositiveButton(R.string.generic_action_remove) { _, _ -> + viewModel.onRemoveCheckInConfirmed(checkIn) + } + setNegativeButton(R.string.generic_action_abort) { _, _ -> /* NOOP */ } + }.show() + private fun setupMenu(toolbar: Toolbar) = toolbar.apply { inflateMenu(R.menu.menu_trace_location_attendee_checkins) setOnMenuItemClickListener { @@ -103,7 +129,7 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag true } R.id.menu_remove_all -> { - Toast.makeText(requireContext(), "Remove all // TODO", Toast.LENGTH_SHORT).show() + viewModel.onRemoveAllCheckIns() true } else -> onOptionsItemSelected(it) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt index a4495a2ef096e1afa4e836422defeba1037349fa..da69a6ca520377209567c60001e2f7e389935047 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt @@ -6,17 +6,20 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeUriParser import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationQRCodeVerifier -import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationVerifyResult import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.items.ActiveCheckInVH import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.items.PastCheckInVH +import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import okio.ByteString.Companion.EMPTY @@ -29,13 +32,18 @@ class CheckInsViewModel @AssistedInject constructor( @Assisted private val savedState: SavedStateHandle, @Assisted private val deepLink: String?, dispatcherProvider: DispatcherProvider, + @AppScope private val appScope: CoroutineScope, private val traceLocationQRCodeVerifier: TraceLocationQRCodeVerifier, - private val qrCodeUriParser: QRCodeUriParser + private val qrCodeUriParser: QRCodeUriParser, + private val checkInsRepository: CheckInRepository, ) : CWAViewModel(dispatcherProvider) { - val confirmationEvent = SingleLiveEvent<TraceLocationVerifyResult>() + val events = SingleLiveEvent<CheckInEvent>() - val checkins = FAKE_CHECKIN_SOURCE + val checkins = combine( + FAKE_CHECKIN_SOURCE, + checkInsRepository.allCheckIns + ) { fake: List<CheckIn>, real: List<CheckIn> -> fake + real } .map { checkins -> checkins.sortedBy { it.checkInEnd } } .map { checkins -> checkins.map { checkin -> @@ -43,13 +51,13 @@ class CheckInsViewModel @AssistedInject constructor( checkin.checkInEnd == null -> ActiveCheckInVH.Item( checkin = checkin, onCardClicked = { /* TODO */ }, - onRemoveItem = { /* TODO */ }, + onRemoveItem = { events.postValue(CheckInEvent.ConfirmRemoveItem(it)) }, onCheckout = { /* TODO */ } ) else -> PastCheckInVH.Item( checkin = checkin, onCardClicked = { /* TODO */ }, - onRemoveItem = { /* TODO */ } + onRemoveItem = { events.postValue(CheckInEvent.ConfirmRemoveItem(it)) } ) } } @@ -68,6 +76,22 @@ class CheckInsViewModel @AssistedInject constructor( savedState.set(SKEY_LAST_DEEPLINK, deepLink) } + fun onRemoveCheckInConfirmed(checkIn: CheckIn?) { + Timber.d("removeCheckin(checkIn=%s)", checkIn) + launch(scope = appScope) { + if (checkIn == null) { + checkInsRepository.clear() + } else { + checkInsRepository.deleteCheckIns(listOf(checkIn)) + } + } + } + + fun onRemoveAllCheckIns() { + Timber.d("onRemovaAllCheckIns()") + events.postValue(CheckInEvent.ConfirmRemoveAll) + } + private fun verifyUri(uri: String) = launch { try { Timber.i("uri: $uri") @@ -76,7 +100,7 @@ class CheckInsViewModel @AssistedInject constructor( val verifyResult = traceLocationQRCodeVerifier.verify(signedTraceLocation.toByteArray()) Timber.i("verifyResult: $verifyResult") - confirmationEvent.postValue(verifyResult) + events.postValue(CheckInEvent.ConfirmCheckIn(verifyResult)) } catch (e: Exception) { Timber.d(e, "TraceLocation verification failed") e.report(ExceptionCategory.INTERNAL) @@ -104,7 +128,7 @@ private val FAKE_CHECKINS = listOf( version = 1, type = 1, description = "Jahrestreffen der deutschen SAP Anwendergruppe", - address = "Hauptstr. 3, 69115 Heidelberg", + address = "Hauptstr. 3, 69115 Heidelberg (FakeEntry)", traceLocationStart = null, traceLocationEnd = null, defaultCheckInLengthInMinutes = 3 * 60, @@ -122,7 +146,7 @@ private val FAKE_CHECKINS = listOf( version = 1, type = 2, description = "CWA Launch Party", - address = "At home! Do you want the 'rona?", + address = "At home! Do you want the 'rona? (FakeEntry)", traceLocationStart = Instant.parse("2021-01-01T12:00:00.000Z"), traceLocationEnd = Instant.parse("2021-01-01T15:00:00.000Z"), defaultCheckInLengthInMinutes = 15, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt index ebd8bcd9bca9a76c21f49f3cb3a360e68a6f4776..0399a7d972ebffd55541b9a571ad17e42a5110fe 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt @@ -3,12 +3,15 @@ package de.rki.coronawarnapp.eventregistration.checkins import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.slot import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first @@ -32,12 +35,37 @@ class CheckInRepositoryTest : BaseTest() { every { factory.create() } returns database every { database.eventCheckInDao() } returns checkInDao every { checkInDao.allEntries() } returns allEntriesFlow + coEvery { checkInDao.entryForId(any()) } coAnswers { + allEntriesFlow.first().singleOrNull { it.id == arg(0) } + } } - private fun createInstance(scope: CoroutineScope) = CheckInRepository( - factory, - scope - ) + private fun createInstance(scope: CoroutineScope) = CheckInRepository(factory) + + @Test + fun `new entities should have ID 0`() = runBlockingTest { + shouldThrow<IllegalArgumentException> { + val checkIn = CheckIn( + id = 0L, + guid = "41da2115-eba2-49bd-bf17-adb3d635ddaf", + guidHash = EMPTY, + version = 1, + type = 2, + description = "brothers birthday", + address = "Malibu", + traceLocationStart = Instant.EPOCH, + traceLocationEnd = null, + defaultCheckInLengthInMinutes = null, + traceLocationBytes = EMPTY, + signature = EMPTY, + checkInStart = Instant.EPOCH, + checkInEnd = Instant.EPOCH, + completed = false, + createJournalEntry = false + ) + createInstance(scope = this).addCheckIn(checkIn) + } + } @Test fun `add new check in`() { @@ -47,7 +75,7 @@ class CheckInRepositoryTest : BaseTest() { val end = Instant.ofEpochMilli(1397210400001) createInstance(scope = this).addCheckIn( CheckIn( - id = 0L, + id = 1L, guid = "41da2115-eba2-49bd-bf17-adb3d635ddaf", guidHash = EMPTY, version = 1, @@ -68,7 +96,7 @@ class CheckInRepositoryTest : BaseTest() { coVerify { checkInDao.insert( TraceLocationCheckInEntity( - id = 0L, + id = 1L, guid = "41da2115-eba2-49bd-bf17-adb3d635ddaf", guidHashBase64 = "", version = 1, @@ -91,53 +119,19 @@ class CheckInRepositoryTest : BaseTest() { } @Test - fun `update new check in`() { - coEvery { checkInDao.update(any()) } returns Unit - runBlockingTest { - val start = Instant.ofEpochMilli(1397210400000) - val end = Instant.ofEpochMilli(1615796487) - createInstance(scope = this).updateCheckIn( - CheckIn( - id = 0L, - guid = "6e5530ce-1afc-4695-a4fc-572e6443eacd", - guidHash = EMPTY, - version = 1, - type = 2, - description = "sisters birthday", - address = "Long Beach", - traceLocationStart = start, - traceLocationEnd = end, - defaultCheckInLengthInMinutes = null, - traceLocationBytes = EMPTY, - signature = EMPTY, - checkInStart = start, - checkInEnd = end, - completed = false, - createJournalEntry = false - ) - ) - coVerify { - checkInDao.update( - TraceLocationCheckInEntity( - id = 0L, - guid = "6e5530ce-1afc-4695-a4fc-572e6443eacd", - guidHashBase64 = "", - version = 1, - type = 2, - description = "sisters birthday", - address = "Long Beach", - traceLocationStart = start, - traceLocationEnd = end, - defaultCheckInLengthInMinutes = null, - traceLocationBytesBase64 = "", - signatureBase64 = "", - checkInStart = start, - checkInEnd = end, - completed = false, - createJournalEntry = false - ) - ) - } + fun `update new check in`() = runBlockingTest { + val slot = slot<(CheckIn) -> CheckIn>() + coEvery { checkInDao.updateEntityById(any(), capture(slot)) } returns Unit + + val checkIn = mockk<CheckIn>() + createInstance(scope = this).updateCheckIn(1L) { + checkIn + } + + slot.captured.invoke(mockk()) shouldBe checkIn + + coVerify { + checkInDao.updateEntityById(1L, any()) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt index 81e9e2f5354fdaf9503bc2c27cb43a118fef4f68..f1514ebd9a61926d303dff9ed372b82331395e47 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt @@ -115,7 +115,7 @@ class SubmissionTaskTest : BaseTest() { every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardHours(1)) every { checkInRepository.allCheckIns } returns flowOf(emptyList()) - every { checkInRepository.clear() } just Runs + coEvery { checkInRepository.clear() } just Runs every { checkInsTransformer.transform(any()) } returns emptyList() }