From 13ac744e45df9cfd36e137874a8c34855a7d6e83 Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Fri, 9 Apr 2021 16:15:14 +0200 Subject: [PATCH] Automatic contact journal entry creation (EXPOSUREAPP-5943) (#2770) * Created contact journal entry creator * reduced complexity & clean * updated CheckOutHandlerTest * First test, others will follow * ktlint * added further tests for location creation * Changed trace location id to ByteString * 42 * ContactDiary ContactJournal contactdiaryoverviewViewModelTest Adjustment * Improved location name, Added test if name gets created as expected * test adjustment * Create location visits if missing * satisfy ci - further tests are coming * clean Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- .../ContactDiaryDatabaseMigrationTest.kt | 3 +- .../storage/ContactDiaryDatabaseTest.kt | 3 +- .../model/ContactDiaryLocation.kt | 3 +- .../model/DefaultContactDiaryLocation.kt | 4 +- .../entity/ContactDiaryLocationEntity.kt | 3 +- .../checkins/checkout/CheckOutHandler.kt | 18 ++- .../ContactJournalCheckInEntryCreator.kt | 104 ++++++++++++++++++ .../util/database/CommonConverters.kt | 8 ++ .../ContactDiaryEditLocationsViewModelTest.kt | 3 +- .../ContactDiaryOverviewViewModelTest.kt | 5 +- .../checkins/checkout/CheckOutHandlerTest.kt | 53 ++++++++- .../ContactJournalCheckInEntryCreatorTest.kt | 87 +++++++++++++++ 12 files changed, 272 insertions(+), 22 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt index dd683c4bb..ca8693d3e 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt @@ -20,6 +20,7 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Duration import org.joda.time.LocalDate import org.junit.Rule @@ -176,7 +177,7 @@ class ContactDiaryDatabaseMigrationTest : BaseTestInstrumentation() { checkInID = null ) - val locationAfter = location.copy(traceLocationID = "jshrgu-aifhioaio-aofsjof-samofp-kjsadngsgf") + val locationAfter = location.copy(traceLocationID = "jshrgu-aifhioaio-aofsjof-samofp-kjsadngsgf".decodeBase64()) val locationVisitAfter = locationVisit.copy(checkInID = 101) val locationValues = ContentValues().apply { diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt index e3573457d..6cdc211e2 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt @@ -14,6 +14,7 @@ import io.kotest.matchers.shouldBe import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Duration import org.joda.time.LocalDate import org.junit.After @@ -37,7 +38,7 @@ class ContactDiaryDatabaseTest : BaseTestInstrumentation() { locationName = "Rewe Wiesloch", emailAddress = "location-emailAddress", phoneNumber = "location-phoneNumber", - traceLocationID = "a-b-c-d" + traceLocationID = "a-b-c-d".decodeBase64() ) private val personEncounter = ContactDiaryPersonEncounterEntity( id = 3, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt index e0486049d..020bb3f95 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.contactdiary.model +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import de.rki.coronawarnapp.util.lists.HasStableId import java.util.Locale @@ -8,7 +9,7 @@ interface ContactDiaryLocation : HasStableId { var locationName: String val phoneNumber: String? val emailAddress: String? - val traceLocationID: String? + val traceLocationID: TraceLocationId? } fun List<ContactDiaryLocation>.sortByNameAndIdASC(): List<ContactDiaryLocation> = diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt index c121e2611..9ebd6f415 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt @@ -1,11 +1,13 @@ package de.rki.coronawarnapp.contactdiary.model +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId + data class DefaultContactDiaryLocation( override val locationId: Long = 0L, override var locationName: String, override val phoneNumber: String? = null, override val emailAddress: String? = null, - override val traceLocationID: String? = null + override val traceLocationID: TraceLocationId? = null ) : ContactDiaryLocation { override val stableId: Long get() = locationId diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt index b09a84c8f..ba4f25464 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt @@ -5,6 +5,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import de.rki.coronawarnapp.util.trimToLength import kotlinx.parcelize.Parcelize @@ -15,7 +16,7 @@ data class ContactDiaryLocationEntity( @ColumnInfo(name = "locationName") override var locationName: String, override val phoneNumber: String?, override val emailAddress: String?, - @ColumnInfo(name = "traceLocationID") override val traceLocationID: String? + @ColumnInfo(name = "traceLocationID") override val traceLocationID: TraceLocationId? ) : ContactDiaryLocation, Parcelable { override val stableId: Long get() = locationId diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt index 917e8c0ec..4852be4a7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt @@ -1,18 +1,18 @@ package de.rki.coronawarnapp.presencetracing.checkins.checkout -import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import dagger.Reusable +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.util.TimeStamper import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton +@Reusable class CheckOutHandler @Inject constructor( private val repository: CheckInRepository, private val timeStamper: TimeStamper, - private val diaryRepository: ContactDiaryRepository, + private val contactJournalCheckInEntryCreator: ContactJournalCheckInEntryCreator ) { /** * Throw **[IllegalArgumentException]** if the check-in does not exist. @@ -21,18 +21,16 @@ class CheckOutHandler @Inject constructor( suspend fun checkOut(checkInId: Long, checkOutAt: Instant = timeStamper.nowUTC) { Timber.d("checkOut(checkInId=$checkInId, checkOutAt=%s)", checkOutAt) - var createJournalEntry = false + var checkIn: CheckIn? = null repository.updateCheckIn(checkInId) { - createJournalEntry = it.createJournalEntry it.copy( checkInEnd = checkOutAt, completed = true - ) + ).also { c -> checkIn = c } } - if (createJournalEntry) { - Timber.d("Creating journal entry for $checkInId") - // TODO Create journal entry + if (checkIn?.createJournalEntry == true) { + contactJournalCheckInEntryCreator.createEntry(checkIn!!) } // Remove auto-checkout timer? diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt new file mode 100644 index 000000000..e553c9834 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt @@ -0,0 +1,104 @@ +package de.rki.coronawarnapp.presencetracing.checkins.checkout + +import androidx.annotation.VisibleForTesting +import dagger.Reusable +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocationVisit +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import de.rki.coronawarnapp.eventregistration.checkins.split.splitByMidnightUTC +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import org.joda.time.Duration +import org.joda.time.Seconds +import org.joda.time.format.DateTimeFormat +import timber.log.Timber +import javax.inject.Inject +import kotlin.math.roundToLong + +@Reusable +class ContactJournalCheckInEntryCreator @Inject constructor( + private val diaryRepository: ContactDiaryRepository +) { + + suspend fun createEntry(checkIn: CheckIn) { + Timber.d("Creating journal entry for %s", checkIn) + + // 1. Create location if missing + val location: ContactDiaryLocation = diaryRepository.locations.first() + .find { it.traceLocationID == checkIn.traceLocationId } ?: checkIn.toLocation() + + // 2. Split CheckIn by Midnight UTC + val splitCheckIns = checkIn.splitByMidnightUTC() + Timber.d("Split %s into %s ", this, splitCheckIns) + + // 3. Create LocationVisit if missing + splitCheckIns + .createMissingLocationVisits(location) + .forEach { diaryRepository.addLocationVisit(it) } + } + + private suspend fun CheckIn.toLocation(): ContactDiaryLocation { + val location = DefaultContactDiaryLocation( + locationName = locationName(), + traceLocationID = traceLocationId + ) + Timber.d("Created new location %s and adding it to contact journal db", location) + return diaryRepository.addLocation(location) // Get location from db cause we need the id autogenerated by db + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun CheckIn.locationName(): String { + val nameParts = mutableListOf(description, address) + + if (traceLocationStart != null && traceLocationEnd != null) { + if (traceLocationStart.millis > 0 && traceLocationEnd.millis > 0) { + val formattedStartDate = traceLocationStart.toUserTimeZone().toString(DateTimeFormat.shortDateTime()) + val formattedEndDate = traceLocationEnd.toUserTimeZone().toString(DateTimeFormat.shortDateTime()) + nameParts.add("$formattedStartDate - $formattedEndDate") + } + } + + return nameParts.joinToString(separator = ", ") + } + + private fun CheckIn.toLocationVisit(location: ContactDiaryLocation): ContactDiaryLocationVisit { + // Use Seconds for more precision + val durationInMinutes = Seconds.secondsBetween(checkInStart, checkInEnd).seconds / 60.0 + val duration = (durationInMinutes / 15).roundToLong() * 15 + return DefaultContactDiaryLocationVisit( + date = checkInStart.toLocalDateUtc(), + contactDiaryLocation = location, + duration = Duration.standardMinutes(duration), + checkInID = id + ) + } + + private suspend fun List<CheckIn>.createMissingLocationVisits(location: ContactDiaryLocation): + List<ContactDiaryLocationVisit> { + Timber.d( + "createMissingLocationVisits(location=%s) for %s", + location, + this.joinToString(prefix = System.lineSeparator(), separator = System.lineSeparator()) + ) + val existingLocationVisits = diaryRepository.locationVisits.firstOrNull() ?: emptyList() + // Existing location visits shall not be updated, so just drop them + return filter { + existingLocationVisits.none { visit -> + visit.date == it.checkInStart.toLocalDateUtc() && + visit.contactDiaryLocation.locationId == location.locationId + } + } + .map { it.toLocationVisit(location) } + .also { + Timber.d( + "Created locations visits: %s", + it.joinToString(prefix = System.lineSeparator(), separator = System.lineSeparator()) + ) + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt index 92a26a40a..4c30c5eb0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt @@ -4,7 +4,9 @@ import androidx.room.TypeConverter import com.google.gson.Gson import com.google.gson.reflect.TypeToken import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import de.rki.coronawarnapp.util.serialization.fromJson +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Instant import org.joda.time.LocalDate import org.joda.time.LocalTime @@ -68,4 +70,10 @@ class CommonConverters { @TypeConverter fun fromLocationCode(code: LocationCode?): String? = code?.identifier + + @TypeConverter + fun toTraceLocationId(value: String?): TraceLocationId? = value?.decodeBase64() + + @TypeConverter + fun fromTraceLocationId(traceLocationId: TraceLocationId?): String? = traceLocationId?.base64() } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt index e8ee5c327..6706173c4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.contactdiary.ui.edit import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationEntity import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -30,7 +31,7 @@ class ContactDiaryEditLocationsViewModelTest { override val phoneNumber: String? = null override val emailAddress: String? = null override val stableId = 1L - override val traceLocationID: String? = null + override val traceLocationID: TraceLocationId? = null } private val locationList = listOf(location) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt index aa7c8b2d9..b50da8d26 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt @@ -35,6 +35,7 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import kotlinx.coroutines.flow.flowOf +import okio.ByteString.Companion.decodeBase64 import org.joda.time.DateTimeZone import org.joda.time.Instant import org.joda.time.LocalDate @@ -83,13 +84,13 @@ open class ContactDiaryOverviewViewModelTest { private val locationEventLowRisk = DefaultContactDiaryLocation( locationId = 456, locationName = "Jahrestreffen der deutschen SAP Anwendergruppe", - traceLocationID = "12ab-34cd-56ef-78gh-456" + traceLocationID = "12ab-34cd-56ef-78gh-456".decodeBase64() ) private val locationEventHighRisk = DefaultContactDiaryLocation( locationId = 457, locationName = "Kiosk", - traceLocationID = "12ab-34cd-56ef-78gh-457" + traceLocationID = "12ab-34cd-56ef-78gh-457".decodeBase64() ) private val locationEventLowRiskVisit = DefaultContactDiaryLocationVisit( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt index 04fbb3a38..07007ff99 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt @@ -1,14 +1,16 @@ package de.rki.coronawarnapp.presencetracing.checkins.checkout -import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.util.TimeStamper 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.just +import io.mockk.runs import kotlinx.coroutines.test.runBlockingTest import okio.ByteString.Companion.encode import org.joda.time.Instant @@ -20,7 +22,7 @@ class CheckOutHandlerTest : BaseTest() { @MockK lateinit var repository: CheckInRepository @MockK lateinit var timeStamper: TimeStamper - @MockK lateinit var diaryRepository: ContactDiaryRepository + @MockK lateinit var contactJournalCheckInEntryCreator: ContactJournalCheckInEntryCreator private val testCheckIn = CheckIn( id = 42L, @@ -39,6 +41,12 @@ class CheckOutHandlerTest : BaseTest() { completed = false, createJournalEntry = true ) + + private val testCheckInDontCreate = testCheckIn.copy( + id = 43L, + createJournalEntry = false + ) + private var updatedCheckIn: CheckIn? = null private val nowUTC = Instant.ofEpochMilli(50) @@ -52,12 +60,19 @@ class CheckOutHandlerTest : BaseTest() { val callback: (CheckIn) -> CheckIn = arg(1) updatedCheckIn = callback(testCheckIn) } + + coEvery { repository.updateCheckIn(43, any()) } coAnswers { + val callback: (CheckIn) -> CheckIn = arg(1) + updatedCheckIn = callback(testCheckInDontCreate) + } + + coEvery { contactJournalCheckInEntryCreator.createEntry(any()) } just runs } private fun createInstance() = CheckOutHandler( repository = repository, timeStamper = timeStamper, - diaryRepository = diaryRepository, + contactJournalCheckInEntryCreator = contactJournalCheckInEntryCreator ) @Test @@ -68,7 +83,37 @@ class CheckOutHandlerTest : BaseTest() { checkInEnd = nowUTC, completed = true ) - // TODO journal creation + + coVerify(exactly = 1) { + contactJournalCheckInEntryCreator.createEntry(any()) + } + // TODO cancel auto checkouts } + + @Test + fun `Creates entry if create journal entry is true`() = runBlockingTest { + createInstance().apply { + checkOut(42) + } + + updatedCheckIn?.createJournalEntry shouldBe true + + coVerify(exactly = 1) { + contactJournalCheckInEntryCreator.createEntry(any()) + } + } + + @Test + fun `Does not create entry if create journal entry is false`() = runBlockingTest { + createInstance().apply { + checkOut(43) + } + + updatedCheckIn?.createJournalEntry shouldBe false + + coVerify(exactly = 0) { + contactJournalCheckInEntryCreator.createEntry(any()) + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt new file mode 100644 index 000000000..58834e6c7 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt @@ -0,0 +1,87 @@ +package de.rki.coronawarnapp.presencetracing.checkins.checkout + +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +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.just +import io.mockk.runs +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import okio.ByteString.Companion.encode +import org.joda.time.Instant +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class ContactJournalCheckInEntryCreatorTest : BaseTest() { + + @MockK lateinit var contactDiaryRepo: ContactDiaryRepository + + private val testCheckIn = CheckIn( + id = 42L, + traceLocationId = "traceLocationId1".encode(), + version = 1, + type = 1, + description = "Restaurant", + address = "Around the corner", + traceLocationStart = Instant.parse("2021-03-04T22:00+01:00"), + traceLocationEnd = Instant.parse("2021-03-04T23:00+01:00"), + defaultCheckInLengthInMinutes = null, + cryptographicSeed = "cryptographicSeed".encode(), + cnPublicKey = "cnPublicKey", + checkInStart = Instant.parse("2021-03-04T22:00+01:00"), + checkInEnd = Instant.parse("2021-03-04T23:00+01:00"), + completed = false, + createJournalEntry = true + ) + + private val testLocation = DefaultContactDiaryLocation( + locationId = 123L, + locationName = "${testCheckIn.description}, ${testCheckIn.address}, ${testCheckIn.traceLocationStart} - ${testCheckIn.traceLocationEnd}", + traceLocationID = testCheckIn.traceLocationId + ) + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { contactDiaryRepo.locations } returns flowOf(emptyList()) + + every { contactDiaryRepo.locationVisits } returns flowOf(emptyList()) + + coEvery { contactDiaryRepo.addLocationVisit(any()) } just runs + + coEvery { contactDiaryRepo.addLocation(any()) } returns testLocation + } + + private fun createInstance() = ContactJournalCheckInEntryCreator( + diaryRepository = contactDiaryRepo + ) + + @Test + fun `Creates location if missing`() = runBlockingTest { + every { contactDiaryRepo.locations } returns flowOf(emptyList()) andThen flowOf(listOf(testLocation)) + + // Repo returns an empty list for the first call, so location is missing and a new location should be created and added + val instance = createInstance() + instance.createEntry(testCheckIn) + + coVerify(exactly = 1) { + contactDiaryRepo.addLocation(any()) + } + + // Location with trace location id already exists, so that location will be used + instance.createEntry(testCheckIn) + instance.createEntry(testCheckIn) + instance.createEntry(testCheckIn) + + coVerify(exactly = 1) { + contactDiaryRepo.addLocation(any()) + } + } +} -- GitLab