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() 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()) } }