Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
Merge branch 'release/2.0.x' into fix/6190-text-missing-bl-off-expo-l…
Browse files Browse the repository at this point in the history
…ogging
  • Loading branch information
SamuraiKek committed Apr 9, 2021
2 parents 9b4007b + 13ac744 commit 3c51001
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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> =
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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?
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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())
}
}
}
Loading

0 comments on commit 3c51001

Please sign in to comment.