med-det-samme/app/src/androidTest/java/no/naiv/meddetsamme/backup/CloudBackupRoundTripTest.kt

100 lines
4.4 KiB
Kotlin
Raw Normal View History

package no.naiv.meddetsamme.backup
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import no.naiv.meddetsamme.data.InventoryItem
import no.naiv.meddetsamme.data.MedDatabase
import no.naiv.meddetsamme.data.MedForm
import no.naiv.meddetsamme.data.Medication
import no.naiv.meddetsamme.settings.SettingsStore
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/**
* The Google Auto Backup path end to end, without the OS backup transport
* (which UI/automation can't drive deterministically): writeCloudBlob encrypts
* with the stored passphrase, pendingCloudRestore fires only on an empty DB,
* and import() with the right passphrase brings the data back. This is the
* "new phone" recovery, and it must keep working Keystore won't migrate, so
* the typed passphrase is the only key.
*/
@RunWith(AndroidJUnit4::class)
class CloudBackupRoundTripTest {
private val context = ApplicationProvider.getApplicationContext<android.content.Context>()
private val db get() = MedDatabase.get(context)
private val manager = BackupManager(context)
@Before fun clear() {
runBlocking { db.restoreAll(emptyList(), emptyList(), emptyList(), emptyList()) }
BackupManager.cloudBlobFile(context).delete()
}
@After fun cleanup() {
runBlocking { db.restoreAll(emptyList(), emptyList(), emptyList(), emptyList()) }
BackupManager.cloudBlobFile(context).delete()
SettingsStore(context).autoBackupPassphrase = null
}
@Test fun writeThenRestoreOnEmptyDb() = runBlocking {
SettingsStore(context).autoBackupPassphrase = "korrekt hest"
val itemId = db.inventoryDao().insert(
InventoryItem(name = "Levaxin", strength = "50 µg", unit = "tablett", form = MedForm.TABLET, stockUnits = 42.0),
)
db.medicationDao().insert(Medication(itemId = itemId, notes = "fastende"))
manager.writeCloudBlob()
val blob = BackupManager.cloudBlobFile(context)
assertTrue("blob written", blob.exists())
assertTrue("blob is an age v1 file", blob.readBytes().decodeToString(0, 21) == "age-encryption.org/v1")
// Non-empty DB → no restore offered.
assertNull(manager.pendingCloudRestore())
// Simulate the new device: empty DB, blob restored by the OS.
db.restoreAll(emptyList(), emptyList(), emptyList(), emptyList())
val pending = manager.pendingCloudRestore()
assertNotNull("restore offered on empty DB", pending)
val count = manager.import(pending!!.readBytes(), "korrekt hest".toCharArray())
assertEquals(1, count)
assertEquals("Levaxin", db.inventoryDao().getAll().single().name)
assertEquals("fastende", db.medicationDao().getAllWithItems().single().med.notes)
}
@Test fun wrongPassphraseFailsAndKeepsDbEmpty() = runBlocking {
SettingsStore(context).autoBackupPassphrase = "riktig"
val itemId = db.inventoryDao().insert(
InventoryItem(name = "Levaxin", strength = "50 µg", unit = "tablett", form = MedForm.TABLET, stockUnits = 42.0),
)
db.medicationDao().insert(Medication(itemId = itemId))
manager.writeCloudBlob()
db.restoreAll(emptyList(), emptyList(), emptyList(), emptyList())
val blob = manager.pendingCloudRestore()!!
try {
manager.import(blob.readBytes(), "feil".toCharArray())
org.junit.Assert.fail("wrong passphrase must throw")
} catch (_: Exception) { /* expected */ }
assertTrue("DB still empty after failed restore", db.inventoryDao().getAll().isEmpty())
}
@Test fun noPassphraseRemovesBlob() = runBlocking {
SettingsStore(context).autoBackupPassphrase = "x"
db.inventoryDao().insert(InventoryItem(name = "A", strength = "", unit = "stk", form = MedForm.OTHER))
manager.writeCloudBlob()
assertTrue(BackupManager.cloudBlobFile(context).exists())
// Clearing the passphrase must stop offering data to Google.
SettingsStore(context).autoBackupPassphrase = null
manager.writeCloudBlob()
assertTrue("blob removed when passphrase cleared", !BackupManager.cloudBlobFile(context).exists())
}
}