From 7957fa686eb6e8cf57bcb62ff4e6d2785693030b Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 10 Oct 2026 16:29:20 +0200 Subject: feat(android): back up WhatsApp's own chat backups, and its media if asked A WhatsApp section takes WhatsApp's folder through the picker, sends its encrypted Databases and Backups (not the dated copies of earlier weeks) into /-whatsapp, and names the restore set in the manifest. Co-Authored-By: Claude Opus 5.5 --- .../test/kotlin/org/meshbay/client/WhatsAppTest.kt | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/WhatsAppTest.kt (limited to 'packages/meshbay-android/app/src/test') diff --git a/packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/WhatsAppTest.kt b/packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/WhatsAppTest.kt new file mode 100644 index 0000000..f3e76fe --- /dev/null +++ b/packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/WhatsAppTest.kt @@ -0,0 +1,57 @@ +package org.meshbay.client + +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.meshbay.client.drive.DriveFile +import org.meshbay.client.drive.DriveKind +import org.meshbay.client.drive.DrivePlan +import org.meshbay.client.drive.WhatsApp +import java.util.TimeZone + +class WhatsAppTest { + private val tree = "content://tree/wa" + + private fun file(sub: String, name: String, size: Long = 10) = + DriveFile(tree, "primary:${WhatsApp.FOLDER}/$sub/$name", sub, name, size, 1000, "application/octet-stream") + + @Test fun `only WhatsApp's own folder can be chosen`() { + assertNull(WhatsApp.refusal("primary:Android/media/com.whatsapp/WhatsApp")) + assertNull(WhatsApp.refusal("primary:Android/media/com.whatsapp.w4b/WhatsApp Business")) + assertEquals("not_whatsapp", WhatsApp.refusal("primary:Android/media/com.whatsapp")) + assertEquals("not_whatsapp", WhatsApp.refusal("primary:Documents")) + assertEquals("not_whatsapp", WhatsApp.refusal("primary:Android/media/com.whatsapp/WhatsApp/Media")) + } + + @Test fun `the chats as they are now, not the dated copies of earlier weeks`() { + assertTrue(WhatsApp.include(file("Databases", "msgstore.db.crypt14"), false)) + assertTrue(WhatsApp.include(file("Databases", "msgstore-increment-1.db.crypt14"), false)) + assertFalse(WhatsApp.include(file("Databases", "msgstore-2026-10-09.1.db.crypt14"), false)) + assertFalse(WhatsApp.include(file("Databases", "msgstore-increment-3-2026-10-09.1.db.crypt14"), false)) + assertTrue(WhatsApp.include(file("Backups", "wa.db.crypt14"), false)) + assertTrue(WhatsApp.include(file("Backups/Stickers", "s.webp"), false)) + } + + @Test fun `media only when asked for, and nothing else of the folder`() { + val photo = file("Media/WhatsApp Images", "IMG-20261010-WA0001.jpg") + assertFalse(WhatsApp.include(photo, false)) + assertTrue(WhatsApp.include(photo, true)) + assertFalse(WhatsApp.include(file(".Shared", "x"), true)) + assertTrue(DriveKind.WHATSAPP.include(photo, JSONObject().put("media", true))) + } + + @Test fun `what a restore needs together is the chats and the backups, not the media`() { + assertTrue(WhatsApp.inRestoreSet(file("Databases", "msgstore.db.crypt14"))) + assertTrue(WhatsApp.inRestoreSet(file("Backups", "wa.db.crypt14"))) + assertFalse(WhatsApp.inRestoreSet(file("Media/WhatsApp Video", "v.mp4"))) + } + + @Test fun `on the node the folders are WhatsApp's own, with no extra level`() { + val out = DrivePlan.plan(listOf(file("Databases", "msgstore.db.crypt14")), "Backups/bob-whatsapp", + mapOf(tree to ""), { null }, emptySet(), TimeZone.getTimeZone("UTC")) { _, _ -> false } + assertEquals("Backups/bob-whatsapp/Databases", out.single().dir) + } +} -- cgit v1.2.3