From 8e8156488a3d34acbb1d01b416279e825ea9a56c Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Thu, 11 Jun 2026 21:20:16 +0200 Subject: [PATCH] Test backup: run every configured mechanism, stop misreporting passphrase-only as unconfigured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user's report: with only a backup passphrase set (no S3), 'Test backup nå' errored 'ikke konfigurert' even though Google Auto Backup works on the passphrase alone. New runConfiguredBackups() writes the Google blob whenever a passphrase exists and uploads to S3 only when those credentials are present, throwing only when NOTHING is set up. The toast now names what actually ran ('Kryptert Google-kopi oppdatert' and/or the bucket key). Two instrumented tests pin both ends: passphrase-only succeeds, nothing-configured throws. Co-Authored-By: Claude Fable 5 --- .../backup/CloudBackupRoundTripTest.kt | 20 +++++++++++++++++++ .../naiv/meddetsamme/backup/BackupManager.kt | 20 +++++++++++++++++++ .../meddetsamme/ui/BackupSettingsScreen.kt | 11 ++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/no/naiv/meddetsamme/backup/CloudBackupRoundTripTest.kt b/app/src/androidTest/java/no/naiv/meddetsamme/backup/CloudBackupRoundTripTest.kt index 8515695..74097bd 100644 --- a/app/src/androidTest/java/no/naiv/meddetsamme/backup/CloudBackupRoundTripTest.kt +++ b/app/src/androidTest/java/no/naiv/meddetsamme/backup/CloudBackupRoundTripTest.kt @@ -86,6 +86,26 @@ class CloudBackupRoundTripTest { assertTrue("DB still empty after failed restore", db.inventoryDao().getAll().isEmpty()) } + @Test fun testBackupWithOnlyPassphraseSucceeds() = runBlocking { + // The user's report: passphrase set, no S3 → "Test backup" must NOT + // error with "not configured"; it writes the Google blob and says so. + SettingsStore(context).autoBackupPassphrase = "x" + db.inventoryDao().insert(InventoryItem(name = "A", strength = "", unit = "stk", form = MedForm.OTHER)) + + val outcome = manager.runConfiguredBackups() + assertTrue("cloud blob written", outcome.cloudBlobWritten) + assertNull("no S3 attempted when unconfigured", outcome.s3Key) + assertTrue(BackupManager.cloudBlobFile(context).exists()) + } + + @Test fun testBackupWithNothingConfiguredThrows() = runBlocking { + SettingsStore(context).autoBackupPassphrase = null + try { + manager.runConfiguredBackups() + org.junit.Assert.fail("must require at least one mechanism") + } catch (_: IllegalArgumentException) { /* expected */ } + } + @Test fun noPassphraseRemovesBlob() = runBlocking { SettingsStore(context).autoBackupPassphrase = "x" db.inventoryDao().insert(InventoryItem(name = "A", strength = "", unit = "stk", form = MedForm.OTHER)) diff --git a/app/src/main/java/no/naiv/meddetsamme/backup/BackupManager.kt b/app/src/main/java/no/naiv/meddetsamme/backup/BackupManager.kt index 7e16f1f..c898e6f 100644 --- a/app/src/main/java/no/naiv/meddetsamme/backup/BackupManager.kt +++ b/app/src/main/java/no/naiv/meddetsamme/backup/BackupManager.kt @@ -104,6 +104,26 @@ class BackupManager(private val context: Context) { return key to s3.get(key) } + /** What a "test backup now" actually accomplished, for an honest message. */ + data class BackupOutcome(val cloudBlobWritten: Boolean, val s3Key: String?) + + /** + * Run every backup mechanism that's actually set up, and report which ran. + * The Google blob needs only a passphrase; S3 needs the full credentials. + * Throws only when NOTHING is configured — so a passphrase-only setup no + * longer reports the misleading "not configured" error. + */ + suspend fun runConfiguredBackups(): BackupOutcome { + val settings = SettingsStore(context) + val hasPassphrase = !settings.autoBackupPassphrase.isNullOrEmpty() + require(hasPassphrase || settings.isBackupConfigured) { "Ingen sikkerhetskopi er satt opp" } + + // Google Auto Backup blob: written whenever a passphrase exists. + writeCloudBlob() + val s3Key = if (settings.isBackupConfigured) runAutoBackup() else null + return BackupOutcome(cloudBlobWritten = hasPassphrase, s3Key = s3Key) + } + /** * Unattended backup → S3. Encrypts with the STORED passphrase if one is * configured (protects against bucket compromise only — see SettingsStore), diff --git a/app/src/main/java/no/naiv/meddetsamme/ui/BackupSettingsScreen.kt b/app/src/main/java/no/naiv/meddetsamme/ui/BackupSettingsScreen.kt index 1e16c71..4d8d784 100644 --- a/app/src/main/java/no/naiv/meddetsamme/ui/BackupSettingsScreen.kt +++ b/app/src/main/java/no/naiv/meddetsamme/ui/BackupSettingsScreen.kt @@ -191,8 +191,15 @@ fun BackupSettingsScreen(nav: Nav) { onClick = { scope.launch(Dispatchers.IO) { try { - val key = BackupManager(context).runAutoBackup() - withContext(Dispatchers.Main) { toast("Lastet opp: $key") } + val r = BackupManager(context).runConfiguredBackups() + val msg = buildString { + if (r.cloudBlobWritten) append("Kryptert Google-kopi oppdatert") + if (r.s3Key != null) { + if (isNotEmpty()) append("\n") + append("Lastet opp til bøtta: ${r.s3Key}") + } + } + withContext(Dispatchers.Main) { toast(msg) } } catch (e: Exception) { withContext(Dispatchers.Main) { toast("Backup feilet: ${e.message}") } }