aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/test
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-10 15:50:39 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-10 15:50:39 +0200
commit1c97ffb91bb4928022e995709d7e55b19cfd18eb (patch)
treeaaa05d17adcd6db4256c01ba79b27dfc7ec96d70 /packages/meshbay-android/app/src/test
parent1521cc1ac0cca7ee8786056f84fa399d5cc268f9 (diff)
downloadmeshbay-1c97ffb91bb4928022e995709d7e55b19cfd18eb.tar.gz
feat(android): back up the files of folders the person chooses
A Files section takes folders through the system picker (no storage permission), refuses DCIM, Pictures and Movies, skips what the photo backup sends, and runs on the photo backup's own runner into <folder>/<account>-drive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-android/app/src/test')
-rw-r--r--packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/DriveTest.kt90
1 files changed, 90 insertions, 0 deletions
diff --git a/packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/DriveTest.kt b/packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/DriveTest.kt
new file mode 100644
index 0000000..e661bd1
--- /dev/null
+++ b/packages/meshbay-android/app/src/test/kotlin/org/meshbay/client/DriveTest.kt
@@ -0,0 +1,90 @@
+package org.meshbay.client
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.meshbay.client.drive.DriveFile
+import org.meshbay.client.drive.DriveLedger
+import org.meshbay.client.drive.DrivePlan
+import org.meshbay.client.photos.PhotoPlan
+import org.meshbay.client.photos.PhotoSource
+import java.util.TimeZone
+
+class DriveTest {
+ @get:Rule val tmp = TemporaryFolder()
+
+ private val utc = TimeZone.getTimeZone("UTC")
+ private val tree = "content://tree/primary%3ADocuments"
+ private val names = mapOf(tree to "Documents")
+
+ private fun file(id: String, sub: String = "", size: Long = 10, modified: Long = 1000) =
+ DriveFile(tree, "primary:Documents/" + (if (sub.isEmpty()) "" else "$sub/") + id, sub, id, size, modified,
+ "application/pdf")
+
+ private fun plan(files: List<DriveFile>, sent: Map<String, DriveLedger.Entry> = emptyMap(),
+ skip: Set<String> = emptySet(), same: Boolean = false) =
+ DrivePlan.plan(files, "Backups/bob-drive", names, { sent[it] }, skip, utc) { _, _ -> same }
+
+ private fun entry(f: DriveFile) = DriveLedger.Entry(f.docId, f.modified, f.size, "h", "d", f.name, 1)
+
+ @Test fun `media folders and a whole volume cannot be chosen`() {
+ assertEquals("whole_storage", DrivePlan.refusal("primary:"))
+ assertEquals("whole_storage", DrivePlan.refusal("1234-ABCD:"))
+ assertEquals("media_folder", DrivePlan.refusal("primary:DCIM"))
+ assertEquals("media_folder", DrivePlan.refusal("primary:DCIM/Camera"))
+ assertEquals("media_folder", DrivePlan.refusal("primary:Pictures/Screenshots"))
+ assertEquals("media_folder", DrivePlan.refusal("1234-ABCD:movies"))
+ assertNull(DrivePlan.refusal("primary:Documents"))
+ assertNull(DrivePlan.refusal("primary:Download/Invoices"))
+ assertNull(DrivePlan.refusal("primary:Documents/Pictures"))
+ }
+
+ @Test fun `files go under the chosen folder's name, sub-folders kept`() {
+ val out = plan(listOf(file("a.pdf"), file("b.pdf", sub = "Tax/2026")))
+ assertEquals(setOf("Backups/bob-drive/Documents", "Backups/bob-drive/Documents/Tax/2026"),
+ out.map { it.dir }.toSet())
+ }
+
+ @Test fun `smallest first, hidden files and photo-backed files left out`() {
+ val out = plan(listOf(file("big.pdf", size = 900), file("small.pdf", size = 5), file(".secret"),
+ file("scan.jpg", sub = "Scans")),
+ skip = setOf("documents/scans/scan.jpg"))
+ assertEquals(listOf("small.pdf", "big.pdf"), out.map { it.name })
+ }
+
+ @Test fun `a file already sent is not sent again, a changed one goes beside it`() {
+ val f = file("a.pdf")
+ assertTrue(plan(listOf(f), sent = mapOf(f.docId to entry(f))).isEmpty())
+ val changed = f.copy(size = 20, modified = 86_400_000)
+ val out = plan(listOf(changed), sent = mapOf(f.docId to entry(f)))
+ assertEquals("a-modified-19700102-000000.pdf", out.single().name)
+ assertTrue(out.single().edited)
+ val touched = f.copy(modified = 5000)
+ assertTrue(plan(listOf(touched), sent = mapOf(f.docId to entry(f)), same = true).isEmpty())
+ }
+
+ @Test fun `names the node would refuse are mended, not dropped`() {
+ assertEquals("report.pdf", DrivePlan.safeName("report.pdf"))
+ assertEquals("a_b_c.txt", DrivePlan.safeName("a:b?c.txt"))
+ assertEquals("file-.env", DrivePlan.safeName(".env"))
+ assertEquals("file-_draft", DrivePlan.safeName("_draft"))
+ assertTrue(PhotoPlan.UPLOAD_NAME.matches(DrivePlan.safeName("x".repeat(300) + ".pdf")))
+ assertTrue(PhotoPlan.UPLOAD_NAME.matches(DrivePlan.safeName("end. ")))
+ }
+
+ @Test fun `the ledger remembers across a restart`() {
+ val path = tmp.newFile("d.jsonl")
+ DriveLedger(path).apply { record(entry(file("a.pdf"))); record(entry(file("a.pdf")).copy(sha256 = "z")) }
+ val again = DriveLedger(path)
+ assertEquals(1, again.size)
+ assertEquals("z", again["primary:Documents/a.pdf"]!!.sha256)
+ }
+
+ @Test fun `a path from before Android 10 is read from the top of its volume`() {
+ assertEquals("Documents/Scans/a.jpg", PhotoSource.volumeRelative("/storage/emulated/0/Documents/Scans/a.jpg"))
+ assertEquals("x/a.jpg", PhotoSource.volumeRelative("/storage/1234-ABCD/x/a.jpg"))
+ }
+}