diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-10 15:50:39 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-10 15:50:39 +0200 |
| commit | 1c97ffb91bb4928022e995709d7e55b19cfd18eb (patch) | |
| tree | aaa05d17adcd6db4256c01ba79b27dfc7ec96d70 /packages/meshbay-android/app/src/test | |
| parent | 1521cc1ac0cca7ee8786056f84fa399d5cc268f9 (diff) | |
| download | meshbay-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.kt | 90 |
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")) + } +} |