From df3e6b83601eb9c712c6ae1b3742984a90b48499 Mon Sep 17 00:00:00 2001
From: Matthias Urhahn <matthias.urhahn@sap.com>
Date: Mon, 15 Feb 2021 15:19:27 +0100
Subject: [PATCH] Contact journal extension database changes (EXPOSUREAPP-5112)
 (#2335)

* Extend database with new contact journal attributes, add migrations and tests.

* Add update mechanism for location visits and person encounters, + tests.

* Adjust test comments
---
 .../2.json                                    |  44 +++-
 .../ContactDiaryDatabaseMigrationTest.kt      | 196 ++++++++++++++++++
 .../storage/ContactDiaryDatabaseTest.kt       | 145 ++++++++++---
 .../storage/ContactDiaryDatabase.kt           |  12 +-
 .../entity/ContactDiaryLocationEntity.kt      |  11 +-
 .../entity/ContactDiaryLocationVisitEntity.kt |  12 +-
 .../ContactDiaryLocationVisitWrapper.kt       |   6 +-
 .../ContactDiaryPersonEncounterEntity.kt      |  17 +-
 .../ContactDiaryPersonEncounterWrapper.kt     |   6 +-
 .../entity/ContactDiaryPersonEntity.kt        |  11 +-
 .../converters/ContactDiaryRoomConverters.kt  |  20 ++
 .../ContactDiaryDatabaseMigration1To2.kt      |  77 +++++++
 .../storage/repo/ContactDiaryRepository.kt    |   2 +
 .../repo/DefaultContactDiaryRepository.kt     |  14 ++
 .../ContactDiaryRoomConvertersTest.kt         |  24 +++
 15 files changed, 551 insertions(+), 46 deletions(-)
 create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConverters.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/migrations/ContactDiaryDatabaseMigration1To2.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConvertersTest.kt

diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.contactdiary.storage.ContactDiaryDatabase/2.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.contactdiary.storage.ContactDiaryDatabase/2.json
index 4f53fc054..7fac999cc 100644
--- a/Corona-Warn-App/schemas/de.rki.coronawarnapp.contactdiary.storage.ContactDiaryDatabase/2.json
+++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.contactdiary.storage.ContactDiaryDatabase/2.json
@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 2,
-    "identityHash": "abaa3557b994e3bc2a61d8ee2edff8ba",
+    "identityHash": "d702472d6dd506b73ff6a7b340686c9a",
     "entities": [
       {
         "tableName": "locations",
@@ -44,7 +44,7 @@
       },
       {
         "tableName": "locationvisits",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `fkLocationId` INTEGER NOT NULL, FOREIGN KEY(`fkLocationId`) REFERENCES `locations`(`locationId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `fkLocationId` INTEGER NOT NULL, `duration` INTEGER, `circumstances` TEXT, FOREIGN KEY(`fkLocationId`) REFERENCES `locations`(`locationId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
         "fields": [
           {
             "fieldPath": "id",
@@ -63,6 +63,18 @@
             "columnName": "fkLocationId",
             "affinity": "INTEGER",
             "notNull": true
+          },
+          {
+            "fieldPath": "duration",
+            "columnName": "duration",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "circumstances",
+            "columnName": "circumstances",
+            "affinity": "TEXT",
+            "notNull": false
           }
         ],
         "primaryKey": {
@@ -135,7 +147,7 @@
       },
       {
         "tableName": "personencounters",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `fkPersonId` INTEGER NOT NULL, FOREIGN KEY(`fkPersonId`) REFERENCES `persons`(`personId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `fkPersonId` INTEGER NOT NULL, `durationClassification` TEXT, `withMask` INTEGER, `wasOutside` INTEGER, `circumstances` TEXT, FOREIGN KEY(`fkPersonId`) REFERENCES `persons`(`personId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
         "fields": [
           {
             "fieldPath": "id",
@@ -154,6 +166,30 @@
             "columnName": "fkPersonId",
             "affinity": "INTEGER",
             "notNull": true
+          },
+          {
+            "fieldPath": "durationClassification",
+            "columnName": "durationClassification",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "withMask",
+            "columnName": "withMask",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "wasOutside",
+            "columnName": "wasOutside",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "circumstances",
+            "columnName": "circumstances",
+            "affinity": "TEXT",
+            "notNull": false
           }
         ],
         "primaryKey": {
@@ -190,7 +226,7 @@
     "views": [],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'abaa3557b994e3bc2a61d8ee2edff8ba')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd702472d6dd506b73ff6a7b340686c9a')"
     ]
   }
 }
\ No newline at end of file
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
new file mode 100644
index 000000000..bd23cf846
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt
@@ -0,0 +1,196 @@
+package de.rki.coronawarnapp.contactdiary.storage
+
+import android.database.sqlite.SQLiteException
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationEntity
+import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationVisitEntity
+import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationVisitWrapper
+import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEncounterEntity
+import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEncounterWrapper
+import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEntity
+import de.rki.coronawarnapp.contactdiary.storage.internal.migrations.ContactDiaryDatabaseMigration1To2
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.matchers.shouldBe
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.joda.time.LocalDate
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseTest
+import java.io.IOException
+
+@RunWith(AndroidJUnit4::class)
+class ContactDiaryDatabaseMigrationTest : BaseTest() {
+    private val DB_NAME = "contactdiary_migration_test.db"
+
+    @get:Rule
+    val helper: MigrationTestHelper = MigrationTestHelper(
+        InstrumentationRegistry.getInstrumentation(),
+        ContactDiaryDatabase::class.java.canonicalName,
+        FrameworkSQLiteOpenHelperFactory()
+    )
+
+    /**
+     * Test migration to add new optional attributes
+     */
+    @Test
+    fun migrate1To2() {
+        helper.createDatabase(DB_NAME, 1).apply {
+            execSQL(
+                """
+                    INSERT INTO "locations" (
+                        "locationId",
+                        "locationName"
+                    ) VALUES (
+                        '1',
+                        'Location1'
+                    );
+                """.trimIndent()
+            )
+            execSQL(
+                """
+                    INSERT INTO "persons" (
+                        "personId",
+                        "fullName"
+                    ) VALUES (
+                        '100',
+                        'Person100'
+                    );
+                """.trimIndent()
+            )
+            execSQL(
+                """
+                    INSERT INTO "locationvisits" (
+                        "id",
+                        "date",
+                        "fkLocationId"
+                    ) VALUES (
+                        '2',
+                        '2020-04-20',
+                        '1'
+                    );
+                """.trimIndent()
+            )
+
+            execSQL(
+                """
+                    INSERT INTO "personencounters" (
+                        "id",
+                        "date",
+                        "fkPersonId"
+                    ) VALUES (
+                        '3',
+                        '2020-12-31',
+                        '100'
+                    );
+                """.trimIndent()
+            )
+
+            close()
+        }
+
+        // Run migration
+        helper.runMigrationsAndValidate(
+            DB_NAME,
+            2,
+            true,
+            ContactDiaryDatabaseMigration1To2
+        )
+
+        val daoDb = ContactDiaryDatabase.Factory(
+            ctx = ApplicationProvider.getApplicationContext()
+        ).create(databaseName = DB_NAME)
+
+        val location = ContactDiaryLocationEntity(
+            locationId = 1,
+            locationName = "Location1",
+            phoneNumber = null,
+            emailAddress = null
+        )
+        runBlocking { daoDb.locationDao().allEntries().first() }.single() shouldBe location
+
+        val person = ContactDiaryPersonEntity(
+            personId = 100,
+            fullName = "Person100",
+            phoneNumber = null,
+            emailAddress = null
+        )
+        runBlocking { daoDb.personDao().allEntries().first() }.single() shouldBe person
+
+        runBlocking {
+            daoDb.locationVisitDao().allEntries().first()
+        }.single() shouldBe ContactDiaryLocationVisitWrapper(
+            contactDiaryLocationEntity = location,
+            contactDiaryLocationVisitEntity = ContactDiaryLocationVisitEntity(
+                id = 2,
+                date = LocalDate.parse("2020-04-20"),
+                fkLocationId = 1,
+                duration = null,
+                circumstances = null
+            )
+        )
+
+        runBlocking {
+            daoDb.personEncounterDao().allEntries().first()
+        }.single() shouldBe ContactDiaryPersonEncounterWrapper(
+            contactDiaryPersonEntity = person,
+            contactDiaryPersonEncounterEntity = ContactDiaryPersonEncounterEntity(
+                id = 3,
+                date = LocalDate.parse("2020-12-31"),
+                fkPersonId = 100,
+                withMask = null,
+                wasOutside = null,
+                durationClassification = null,
+                circumstances = null
+            )
+        )
+    }
+
+    @Test
+    fun migrate1To2_failure_throws() {
+        helper.createDatabase(DB_NAME, 1).apply {
+            execSQL("DROP TABLE IF EXISTS locations")
+            execSQL("DROP TABLE IF EXISTS locationvisits")
+            execSQL("DROP TABLE IF EXISTS persons")
+            execSQL("DROP TABLE IF EXISTS personencounters")
+            // Has incompatible existing column phoneNumber of wrong type
+            execSQL("CREATE TABLE IF NOT EXISTS `locations` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `locationName` TEXT NOT NULL, `phoneNumber` INTEGER )")
+            execSQL("CREATE TABLE IF NOT EXISTS `persons` (`personId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fullName` TEXT NOT NULL)")
+            execSQL("CREATE TABLE IF NOT EXISTS `locationvisits` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `fkLocationId` INTEGER NOT NULL, FOREIGN KEY(`fkLocationId`) REFERENCES `locations`(`locationId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)")
+            execSQL("CREATE TABLE IF NOT EXISTS `personencounters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `fkPersonId` INTEGER NOT NULL, FOREIGN KEY(`fkPersonId`) REFERENCES `persons`(`personId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)")
+            close()
+        }
+
+        shouldThrow<SQLiteException> {
+            // Run migration
+            helper.runMigrationsAndValidate(
+                DB_NAME,
+                2,
+                true,
+                ContactDiaryDatabaseMigration1To2
+            )
+        }
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun migrateAll() {
+        helper.createDatabase(DB_NAME, 1).apply {
+            close()
+        }
+
+        // Open latest version of the database. Room will validate the schema
+        // once all migrations execute.
+        ContactDiaryDatabase.Factory(
+            ctx = ApplicationProvider.getApplicationContext()
+        ).create(databaseName = DB_NAME).apply {
+            openHelper.writableDatabase
+            close()
+        }
+    }
+}
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 11fb352a5..a0ea106ef 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
@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.contactdiary.storage
 import androidx.room.Room
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter
 import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationEntity
 import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationVisitEntity
 import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationVisitWrapper
@@ -13,6 +14,7 @@ import io.kotest.matchers.shouldBe
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.runBlocking
+import org.joda.time.Duration
 import org.joda.time.LocalDate
 import org.junit.After
 import org.junit.Test
@@ -24,18 +26,40 @@ class ContactDiaryDatabaseTest : BaseTest() {
 
     // TestData
     private val date = LocalDate.now()
-    private val person = ContactDiaryPersonEntity(personId = 1, fullName = "Peter")
-    private val location = ContactDiaryLocationEntity(locationId = 2, locationName = "Rewe Wiesloch")
-    private val personEncounter = ContactDiaryPersonEncounterEntity(id = 3, date = date, fkPersonId = person.personId)
-    private val locationVisit = ContactDiaryLocationVisitEntity(id = 4, date = date, fkLocationId = location.locationId)
+    private val person = ContactDiaryPersonEntity(
+        personId = 1,
+        fullName = "Peter",
+        emailAddress = "person-emailAddress",
+        phoneNumber = "person-phoneNumber"
+    )
+    private val location = ContactDiaryLocationEntity(
+        locationId = 2,
+        locationName = "Rewe Wiesloch",
+        emailAddress = "location-emailAddress",
+        phoneNumber = "location-phoneNumber"
+    )
+    private val personEncounter = ContactDiaryPersonEncounterEntity(
+        id = 3,
+        date = date,
+        fkPersonId = person.personId,
+        durationClassification = ContactDiaryPersonEncounter.DurationClassification.MORE_THAN_15_MINUTES,
+        withMask = true,
+        wasOutside = false,
+        circumstances = "You could see the smile under his mask."
+    )
+    private val locationVisit = ContactDiaryLocationVisitEntity(
+        id = 4,
+        date = date,
+        fkLocationId = location.locationId,
+        duration = Duration.standardMinutes(99).millis,
+        circumstances = "I had to buy snacks."
+    )
 
     // DB
-    private val contactDiaryDatabase: ContactDiaryDatabase =
-        Room.inMemoryDatabaseBuilder(
-            ApplicationProvider.getApplicationContext(),
-            ContactDiaryDatabase::class.java
-        )
-            .build()
+    private val contactDiaryDatabase: ContactDiaryDatabase = Room.inMemoryDatabaseBuilder(
+        ApplicationProvider.getApplicationContext(),
+        ContactDiaryDatabase::class.java
+    ).build()
 
     private val personDao = contactDiaryDatabase.personDao()
     private val locationDao = contactDiaryDatabase.locationDao()
@@ -97,14 +121,38 @@ class ContactDiaryDatabaseTest : BaseTest() {
     fun getCorrectEntityForDate() = runBlocking {
         val yesterday = LocalDate.now().minusDays(1)
         val tomorrow = LocalDate.now().plusDays(1)
-        val personEncounterYesterday =
-            ContactDiaryPersonEncounterEntity(id = 5, date = yesterday, fkPersonId = person.personId)
-        val personEncounterTomorrow =
-            ContactDiaryPersonEncounterEntity(id = 6, date = tomorrow, fkPersonId = person.personId)
-        val locationVisitYesterday =
-            ContactDiaryLocationVisitEntity(id = 7, date = yesterday, fkLocationId = location.locationId)
-        val locationVisitTomorrow =
-            ContactDiaryLocationVisitEntity(id = 8, date = tomorrow, fkLocationId = location.locationId)
+        val personEncounterYesterday = ContactDiaryPersonEncounterEntity(
+            id = 5,
+            date = yesterday,
+            fkPersonId = person.personId,
+            durationClassification = ContactDiaryPersonEncounter.DurationClassification.LESS_THAN_15_MINUTES,
+            withMask = false,
+            wasOutside = false,
+            circumstances = "encounter-yesterday"
+        )
+        val personEncounterTomorrow = ContactDiaryPersonEncounterEntity(
+            id = 6,
+            date = tomorrow,
+            fkPersonId = person.personId,
+            durationClassification = ContactDiaryPersonEncounter.DurationClassification.MORE_THAN_15_MINUTES,
+            withMask = true,
+            wasOutside = true,
+            circumstances = "encounter-today"
+        )
+        val locationVisitYesterday = ContactDiaryLocationVisitEntity(
+            id = 7,
+            date = yesterday,
+            fkLocationId = location.locationId,
+            duration = Duration.standardMinutes(42).millis,
+            circumstances = "visit-yesterday"
+        )
+        val locationVisitTomorrow = ContactDiaryLocationVisitEntity(
+            id = 8,
+            date = tomorrow,
+            fkLocationId = location.locationId,
+            duration = Duration.standardMinutes(1).millis,
+            circumstances = "visit-today"
+        )
         val encounterList = listOf(personEncounter, personEncounterYesterday, personEncounterTomorrow)
         val visitList = listOf(locationVisit, locationVisitYesterday, locationVisitTomorrow)
         val personEncounterFlow = personEncounterDao.allEntries().map { it.toContactDiaryPersonEncounterEntityList() }
@@ -119,12 +167,61 @@ class ContactDiaryDatabaseTest : BaseTest() {
         personEncounterFlow.first() shouldBe encounterList
         locationVisitFlow.first() shouldBe visitList
 
-        personEncounterDao.entitiesForDate(yesterday).first().toContactDiaryPersonEncounterEntityList() shouldBe listOf(personEncounterYesterday)
-        personEncounterDao.entitiesForDate(date).first().toContactDiaryPersonEncounterEntityList() shouldBe listOf(personEncounter)
-        personEncounterDao.entitiesForDate(tomorrow).first().toContactDiaryPersonEncounterEntityList() shouldBe listOf(personEncounterTomorrow)
+        personEncounterDao.entitiesForDate(yesterday).first().toContactDiaryPersonEncounterEntityList() shouldBe listOf(
+            personEncounterYesterday
+        )
+        personEncounterDao.entitiesForDate(date).first().toContactDiaryPersonEncounterEntityList() shouldBe listOf(
+            personEncounter
+        )
+        personEncounterDao.entitiesForDate(tomorrow).first().toContactDiaryPersonEncounterEntityList() shouldBe listOf(
+            personEncounterTomorrow
+        )
+
+        locationVisitDao.entitiesForDate(yesterday).first().toContactDiaryLocationVisitEntityList() shouldBe listOf(
+            locationVisitYesterday
+        )
+        locationVisitDao.entitiesForDate(date).first().toContactDiaryLocationVisitEntityList() shouldBe listOf(
+            locationVisit
+        )
+        locationVisitDao.entitiesForDate(tomorrow).first().toContactDiaryLocationVisitEntityList() shouldBe listOf(
+            locationVisitTomorrow
+        )
+    }
+
+    @Test
+    fun updatingLocationVisits(): Unit = runBlocking {
+        val locationVisitFlow = locationVisitDao.allEntries().map { it.toContactDiaryLocationVisitEntityList() }
+
+        locationDao.insert(location)
+        locationVisitDao.insert(listOf(locationVisit))
+
+        locationVisitFlow.first().single() shouldBe locationVisit
+
+        val updatedLocation = locationVisit.copy(
+            duration = 123L,
+            circumstances = "Suspicious"
+        )
+        locationVisitDao.update(updatedLocation)
+
+        locationVisitFlow.first().single() shouldBe updatedLocation
+    }
 
-        locationVisitDao.entitiesForDate(yesterday).first().toContactDiaryLocationVisitEntityList() shouldBe listOf(locationVisitYesterday)
-        locationVisitDao.entitiesForDate(date).first().toContactDiaryLocationVisitEntityList() shouldBe listOf(locationVisit)
-        locationVisitDao.entitiesForDate(tomorrow).first().toContactDiaryLocationVisitEntityList() shouldBe listOf(locationVisitTomorrow)
+    @Test
+    fun updatingPersonEncounters(): Unit = runBlocking {
+        val personEncounterFlow = personEncounterDao.allEntries().map { it.toContactDiaryPersonEncounterEntityList() }
+
+        personDao.insert(person)
+        personEncounterDao.insert(personEncounter)
+
+        personEncounterFlow.first().single() shouldBe personEncounter
+
+        val updatedEncounter = personEncounter.copy(
+            withMask = true,
+            wasOutside = false,
+            durationClassification = ContactDiaryPersonEncounter.DurationClassification.MORE_THAN_15_MINUTES,
+            circumstances = "He lend me a coffee cup but the handle broke and it dropped onto my laptop."
+        )
+        personEncounterDao.update(updatedEncounter)
+        personEncounterFlow.first().single() shouldBe updatedEncounter
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabase.kt
index fa8e6a748..bb7eb9c19 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabase.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabase.kt
@@ -13,6 +13,8 @@ import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationEnti
 import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationVisitEntity
 import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEncounterEntity
 import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEntity
+import de.rki.coronawarnapp.contactdiary.storage.internal.converters.ContactDiaryRoomConverters
+import de.rki.coronawarnapp.contactdiary.storage.internal.migrations.ContactDiaryDatabaseMigration1To2
 import de.rki.coronawarnapp.util.database.CommonConverters
 import de.rki.coronawarnapp.util.di.AppContext
 import javax.inject.Inject
@@ -24,10 +26,10 @@ import javax.inject.Inject
         ContactDiaryPersonEntity::class,
         ContactDiaryPersonEncounterEntity::class
     ],
-    version = 2, // TODO check migration patterns
+    version = 2,
     exportSchema = true
 )
-@TypeConverters(CommonConverters::class)
+@TypeConverters(CommonConverters::class, ContactDiaryRoomConverters::class)
 abstract class ContactDiaryDatabase : RoomDatabase() {
 
     abstract fun locationDao(): ContactDiaryLocationDao
@@ -36,9 +38,9 @@ abstract class ContactDiaryDatabase : RoomDatabase() {
     abstract fun personEncounterDao(): ContactDiaryPersonEncounterDao
 
     class Factory @Inject constructor(@AppContext private val ctx: Context) {
-        fun create(): ContactDiaryDatabase = Room
-            .databaseBuilder(ctx, ContactDiaryDatabase::class.java, CONTACT_DIARY_DATABASE_NAME)
-            .fallbackToDestructiveMigration() // TODO we increased schema version, need to migrate?
+        fun create(databaseName: String = CONTACT_DIARY_DATABASE_NAME): ContactDiaryDatabase = Room
+            .databaseBuilder(ctx, ContactDiaryDatabase::class.java, databaseName)
+            .addMigrations(ContactDiaryDatabaseMigration1To2)
             .build()
     }
 
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 dc68c8512..c7e21989f 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
@@ -12,12 +12,17 @@ import kotlinx.parcelize.Parcelize
 data class ContactDiaryLocationEntity(
     @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "locationId") override val locationId: Long = 0L,
     @ColumnInfo(name = "locationName") override var locationName: String,
-    override val phoneNumber: String? = null,
-    override val emailAddress: String? = null
+    override val phoneNumber: String?,
+    override val emailAddress: String?
 ) : ContactDiaryLocation, Parcelable {
     override val stableId: Long
         get() = locationId
 }
 
 fun ContactDiaryLocation.toContactDiaryLocationEntity(): ContactDiaryLocationEntity =
-    ContactDiaryLocationEntity(this.locationId, this.locationName)
+    ContactDiaryLocationEntity(
+        locationId = this.locationId,
+        locationName = this.locationName,
+        phoneNumber = this.phoneNumber,
+        emailAddress = this.emailAddress
+    )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitEntity.kt
index 3bfdd7541..84195ab51 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitEntity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitEntity.kt
@@ -25,8 +25,16 @@ import org.joda.time.LocalDate
 data class ContactDiaryLocationVisitEntity(
     @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Long = 0L,
     @ColumnInfo(name = "date") val date: LocalDate,
-    @ColumnInfo(name = "fkLocationId") val fkLocationId: Long
+    @ColumnInfo(name = "fkLocationId") val fkLocationId: Long,
+    @ColumnInfo(name = "duration") val duration: Long?,
+    @ColumnInfo(name = "circumstances") val circumstances: String?
 )
 
 fun ContactDiaryLocationVisit.toContactDiaryLocationVisitEntity(): ContactDiaryLocationVisitEntity =
-    ContactDiaryLocationVisitEntity(id = this.id, date = this.date, fkLocationId = this.contactDiaryLocation.locationId)
+    ContactDiaryLocationVisitEntity(
+        id = this.id,
+        date = this.date,
+        fkLocationId = this.contactDiaryLocation.locationId,
+        duration = this.duration,
+        circumstances = this.circumstances
+    )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitWrapper.kt
index 333659e2c..040de1b5c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitWrapper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationVisitWrapper.kt
@@ -6,7 +6,7 @@ import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit
 import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocationVisit
 import de.rki.coronawarnapp.contactdiary.model.sortByNameAndIdASC
 
-class ContactDiaryLocationVisitWrapper(
+data class ContactDiaryLocationVisitWrapper(
     @Embedded val contactDiaryLocationVisitEntity: ContactDiaryLocationVisitEntity,
     @Relation(parentColumn = "fkLocationId", entityColumn = "locationId")
     val contactDiaryLocationEntity: ContactDiaryLocationEntity
@@ -16,7 +16,9 @@ fun ContactDiaryLocationVisitWrapper.toContactDiaryLocationVisit(): ContactDiary
     DefaultContactDiaryLocationVisit(
         id = this.contactDiaryLocationVisitEntity.id,
         date = this.contactDiaryLocationVisitEntity.date,
-        contactDiaryLocation = this.contactDiaryLocationEntity
+        contactDiaryLocation = this.contactDiaryLocationEntity,
+        duration = contactDiaryLocationVisitEntity.duration,
+        circumstances = contactDiaryLocationVisitEntity.circumstances
     )
 
 fun List<ContactDiaryLocationVisitWrapper>.toContactDiaryLocationVisitSortedList(): List<ContactDiaryLocationVisit> =
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterEntity.kt
index a98ba8661..35e70d6f7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterEntity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterEntity.kt
@@ -6,6 +6,7 @@ import androidx.room.ForeignKey
 import androidx.room.Index
 import androidx.room.PrimaryKey
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter
+import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter.DurationClassification
 import org.joda.time.LocalDate
 
 @Entity(
@@ -25,8 +26,20 @@ import org.joda.time.LocalDate
 data class ContactDiaryPersonEncounterEntity(
     @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Long = 0L,
     @ColumnInfo(name = "date") val date: LocalDate,
-    @ColumnInfo(name = "fkPersonId") val fkPersonId: Long
+    @ColumnInfo(name = "fkPersonId") val fkPersonId: Long,
+    @ColumnInfo(name = "durationClassification") val durationClassification: DurationClassification?,
+    @ColumnInfo(name = "withMask") val withMask: Boolean?,
+    @ColumnInfo(name = "wasOutside") val wasOutside: Boolean?,
+    @ColumnInfo(name = "circumstances") val circumstances: String?
 )
 
 fun ContactDiaryPersonEncounter.toContactDiaryPersonEncounterEntity(): ContactDiaryPersonEncounterEntity =
-    ContactDiaryPersonEncounterEntity(id = this.id, date = this.date, fkPersonId = this.contactDiaryPerson.personId)
+    ContactDiaryPersonEncounterEntity(
+        id = this.id,
+        date = this.date,
+        fkPersonId = this.contactDiaryPerson.personId,
+        durationClassification = this.durationClassification,
+        withMask = this.withMask,
+        wasOutside = this.wasOutside,
+        circumstances = this.circumstances
+    )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterWrapper.kt
index 26acf434f..847818352 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterWrapper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEncounterWrapper.kt
@@ -16,7 +16,11 @@ fun ContactDiaryPersonEncounterWrapper.toContactDiaryPersonEncounter(): ContactD
     DefaultContactDiaryPersonEncounter(
         id = this.contactDiaryPersonEncounterEntity.id,
         date = this.contactDiaryPersonEncounterEntity.date,
-        contactDiaryPerson = contactDiaryPersonEntity
+        contactDiaryPerson = this.contactDiaryPersonEntity,
+        durationClassification = this.contactDiaryPersonEncounterEntity.durationClassification,
+        withMask = this.contactDiaryPersonEncounterEntity.withMask,
+        wasOutside = this.contactDiaryPersonEncounterEntity.wasOutside,
+        circumstances = this.contactDiaryPersonEncounterEntity.circumstances
     )
 
 fun List<ContactDiaryPersonEncounterWrapper>.toContactDiaryPersonEncounterSortedList():
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt
index 458988fee..f9a10824c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt
@@ -12,12 +12,17 @@ import kotlinx.parcelize.Parcelize
 data class ContactDiaryPersonEntity(
     @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "personId") override val personId: Long = 0L,
     @ColumnInfo(name = "fullName") override var fullName: String,
-    override val phoneNumber: String? = null,
-    override val emailAddress: String? = null
+    @ColumnInfo(name = "phoneNumber") override val phoneNumber: String?,
+    @ColumnInfo(name = "emailAddress") override val emailAddress: String?
 ) : ContactDiaryPerson, Parcelable {
     override val stableId: Long
         get() = personId
 }
 
 fun ContactDiaryPerson.toContactDiaryPersonEntity(): ContactDiaryPersonEntity =
-    ContactDiaryPersonEntity(this.personId, this.fullName)
+    ContactDiaryPersonEntity(
+        personId = this.personId,
+        fullName = this.fullName,
+        phoneNumber = this.phoneNumber,
+        emailAddress = this.emailAddress
+    )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConverters.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConverters.kt
new file mode 100644
index 000000000..5189e70c6
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConverters.kt
@@ -0,0 +1,20 @@
+package de.rki.coronawarnapp.contactdiary.storage.internal.converters
+
+import androidx.room.TypeConverter
+import com.google.gson.Gson
+import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter
+
+class ContactDiaryRoomConverters {
+    private val gson = Gson()
+
+    @TypeConverter
+    fun toContactDurationClassification(value: String?): ContactDiaryPersonEncounter.DurationClassification? {
+        if (value == null) return null
+        return ContactDiaryPersonEncounter.DurationClassification.values().singleOrNull { it.key == value }
+    }
+
+    @TypeConverter
+    fun fromContactDurationClassification(value: ContactDiaryPersonEncounter.DurationClassification?): String? {
+        return value?.key
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/migrations/ContactDiaryDatabaseMigration1To2.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/migrations/ContactDiaryDatabaseMigration1To2.kt
new file mode 100644
index 000000000..ca5ad82ca
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/internal/migrations/ContactDiaryDatabaseMigration1To2.kt
@@ -0,0 +1,77 @@
+package de.rki.coronawarnapp.contactdiary.storage.internal.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.reporting.report
+import timber.log.Timber
+
+/**
+ * Migrates the contact diary database from schema version 1 to schema version 2.
+ * We are adding additional columns for new optional attributes
+ * Person: PhoneNumber, Email
+ * Location: PhoneNumber, Email
+ * PersonEncounter: DurationType, Mask?, Outside?, Comment
+ * LocationVisit: Duration, Comment
+ */
+object ContactDiaryDatabaseMigration1To2 : Migration(1, 2) {
+
+    override fun migrate(database: SupportSQLiteDatabase) {
+        try {
+            Timber.i("Attempting migration 1->2...")
+            performMigration(database)
+            Timber.i("Migration 1->2 successful.")
+        } catch (e: Exception) {
+            Timber.e(e, "Migration 1->2 failed")
+            e.report(ExceptionCategory.INTERNAL, "ContactDiary database migration failed.")
+            throw e
+        }
+    }
+
+    private fun performMigration(database: SupportSQLiteDatabase) = with(database) {
+        Timber.d("Running MIGRATION_1_2")
+
+        migratePersonsTable()
+        migrateLocationsTable()
+        migratePersonEncounterTable()
+        migrateLocationVisitTable()
+    }
+
+    private val migratePersonsTable: SupportSQLiteDatabase.() -> Unit = {
+        Timber.d("Table 'persons': Add column 'phoneNumber'")
+        execSQL("ALTER TABLE `persons` ADD COLUMN `phoneNumber` TEXT")
+
+        Timber.d("Table 'emailAddress': Add column 'phoneNumber'")
+        execSQL("ALTER TABLE `persons` ADD COLUMN `emailAddress` TEXT")
+    }
+
+    private val migrateLocationsTable: SupportSQLiteDatabase.() -> Unit = {
+        Timber.d("Table 'locations': Add column 'phoneNumber'")
+        execSQL("ALTER TABLE `locations` ADD COLUMN `phoneNumber` TEXT")
+
+        Timber.d("Table 'locations': Add column 'emailAddress'")
+        execSQL("ALTER TABLE `locations` ADD COLUMN `emailAddress` TEXT")
+    }
+
+    private val migratePersonEncounterTable: SupportSQLiteDatabase.() -> Unit = {
+        Timber.d("Table 'personencounters': Add column 'durationClassification'")
+        execSQL("ALTER TABLE `personencounters` ADD COLUMN `durationClassification` TEXT")
+
+        Timber.d("Table 'personencounters': Add column 'circumstances'")
+        execSQL("ALTER TABLE `personencounters` ADD COLUMN `circumstances` TEXT")
+
+        Timber.d("Table 'personencounters': Add column 'withMask'")
+        execSQL("ALTER TABLE `personencounters` ADD COLUMN `withMask` INTEGER")
+
+        Timber.d("Table 'personencounters': Add column 'wasOutside'")
+        execSQL("ALTER TABLE `personencounters` ADD COLUMN `wasOutside` INTEGER")
+    }
+
+    private val migrateLocationVisitTable: SupportSQLiteDatabase.() -> Unit = {
+        Timber.d("Table 'locationvisits': Add column 'duration'")
+        execSQL("ALTER TABLE `locationvisits` ADD COLUMN `duration` INTEGER")
+
+        Timber.d("Table 'locationvisits': Add column 'circumstances'")
+        execSQL("ALTER TABLE `locationvisits` ADD COLUMN `circumstances` TEXT")
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt
index 286e5d9ed..d2685422c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt
@@ -22,6 +22,7 @@ interface ContactDiaryRepository {
     val locationVisits: Flow<List<ContactDiaryLocationVisit>>
     fun locationVisitsForDate(date: LocalDate): Flow<List<ContactDiaryLocationVisit>>
     suspend fun addLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit)
+    suspend fun updateLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit)
     suspend fun deleteLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit)
     suspend fun deleteLocationVisits(contactDiaryLocationVisits: List<ContactDiaryLocationVisit>)
     suspend fun deleteAllLocationVisits()
@@ -38,6 +39,7 @@ interface ContactDiaryRepository {
     val personEncounters: Flow<List<ContactDiaryPersonEncounter>>
     fun personEncountersForDate(date: LocalDate): Flow<List<ContactDiaryPersonEncounter>>
     suspend fun addPersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter)
+    suspend fun updatePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter)
     suspend fun deletePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter)
     suspend fun deletePersonEncounters(contactDiaryPersonEncounters: List<ContactDiaryPersonEncounter>)
     suspend fun deleteAllPersonEncounters()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt
index 6cdec5daf..b4a1e1efa 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt
@@ -108,6 +108,13 @@ class DefaultContactDiaryRepository @Inject constructor(
         contactDiaryLocationVisitDao.insert(contactDiaryLocationVisitEntity)
     }
 
+    override suspend fun updateLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit) {
+        executeWhenIdNotDefault(contactDiaryLocationVisit.id) {
+            val contactDiaryLocationVisitEntity = contactDiaryLocationVisit.toContactDiaryLocationVisitEntity()
+            contactDiaryLocationVisitDao.update(contactDiaryLocationVisitEntity)
+        }
+    }
+
     override suspend fun deleteLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit) {
         Timber.d("Deleting location visit $contactDiaryLocationVisit")
         executeWhenIdNotDefault(contactDiaryLocationVisit.id) {
@@ -195,6 +202,13 @@ class DefaultContactDiaryRepository @Inject constructor(
         contactDiaryPersonEncounterDao.insert(contactDiaryPersonEncounterEntity)
     }
 
+    override suspend fun updatePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter) {
+        executeWhenIdNotDefault(contactDiaryPersonEncounter.id) {
+            val contactDiaryPersonEncounterEntity = contactDiaryPersonEncounter.toContactDiaryPersonEncounterEntity()
+            contactDiaryPersonEncounterDao.update(contactDiaryPersonEncounterEntity)
+        }
+    }
+
     override suspend fun deletePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter) {
         Timber.d("Deleting person encounter $contactDiaryPersonEncounter")
         executeWhenIdNotDefault(contactDiaryPersonEncounter.id) {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConvertersTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConvertersTest.kt
new file mode 100644
index 000000000..18b89cc2e
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/storage/internal/converters/ContactDiaryRoomConvertersTest.kt
@@ -0,0 +1,24 @@
+package de.rki.coronawarnapp.contactdiary.storage.internal.converters
+
+import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter.DurationClassification
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class ContactDiaryRoomConvertersTest : BaseTest() {
+
+    @Test
+    fun `test person encounter duration classification`() {
+        ContactDiaryRoomConverters().apply {
+            toContactDurationClassification(null) shouldBe null
+            toContactDurationClassification(
+                DurationClassification.MORE_THAN_15_MINUTES.key
+            ) shouldBe DurationClassification.MORE_THAN_15_MINUTES
+
+            fromContactDurationClassification(null) shouldBe null
+            fromContactDurationClassification(
+                DurationClassification.LESS_THAN_15_MINUTES
+            ) shouldBe DurationClassification.LESS_THAN_15_MINUTES.key
+        }
+    }
+}
-- 
GitLab