1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package org.meshbay.client
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Test
import org.meshbay.client.phonesync.Destination
import org.meshbay.client.phonesync.DestinationChannels
import org.meshbay.client.phonesync.DocPlan
import org.json.JSONArray
import org.meshbay.client.bridge.Refused
import java.util.TimeZone
class DocSyncTest {
private val utc = TimeZone.getTimeZone("UTC")
// 2026-10-09 12:00:00 UTC
private val oct9 = 1791547200000L
private fun settings(account: String = "bob", folder: String = "Backups") = JSONObject()
.put("account", account).put("groupId", "g1").put("groupName", "Mine").put("folder", folder)
@Test fun `each kind goes into a folder of its own named for the account`() {
val c = Destination.fromJson(settings(folder = "/Backups/Phone/"))
assertEquals("Backups/Phone/bob-contacts", DocPlan.dirFor(c, "contacts"))
assertEquals("Backups/Phone/bob-messages", DocPlan.dirFor(c, "messages"))
}
@Test fun `a file is named for when it was taken, in the phone's time`() {
assertEquals("contacts-2026-10-09-1200.vcf", DocPlan.nameFor("contacts", "vcf", oct9, utc))
assertEquals("contacts-2026-10-09-1400.vcf",
DocPlan.nameFor("contacts", "vcf", oct9, TimeZone.getTimeZone("Europe/Paris")))
}
@Test fun `a folder that climbs out or an account that is not a name is refused`() {
for (bad in listOf("../etc", "Backups/../x", "Backups//x", "./x", "")) {
assertThrows(bad, IllegalArgumentException::class.java) { Destination.fromJson(settings(folder = bad)) }
}
for (bad in listOf("../bob", "bob/x", "", "-bob")) {
assertThrows(bad, IllegalArgumentException::class.java) { Destination.fromJson(settings(account = bad)) }
}
}
@Test fun `settings survive being stored`() {
val c = Destination.fromJson(settings())
assertEquals(c, Destination.parse(c.toJson().toString()))
assertNull(Destination.parse("{not json"))
}
@Test fun `another group or folder is another backup`() {
val a = Destination.fromJson(settings())
assertNotEquals(a.ledgerKey, Destination.fromJson(settings(folder = "Other")).ledgerKey)
assertNotEquals(a.ledgerKey, Destination.fromJson(settings().put("groupId", "g2")).ledgerKey)
assertEquals(a.ledgerKey, Destination.fromJson(settings().put("groupName", "Renamed")).ledgerKey)
}
@Test fun `every kind is told when the destination moves, and only then`() {
val store = DestinationChannels(FakePrefs())
var told = 0
store.onChange { told++ }
store.call("phonesync:set-destination", JSONArray().put(settings()))
assertEquals(1, told)
store.call("phonesync:set-destination", JSONArray().put(settings().put("groupName", "Renamed")))
assertEquals("a new name for the same place is not a new backup", 1, told)
store.call("phonesync:set-destination", JSONArray().put(settings(folder = "Other")))
assertEquals(2, told)
store.call("phonesync:set-destination", JSONArray())
assertEquals(3, told)
assertNull(store.get())
}
@Test fun `a destination the page got wrong is refused and the old one kept`() {
val store = DestinationChannels(FakePrefs())
store.call("phonesync:set-destination", JSONArray().put(settings()))
assertThrows(Refused::class.java) {
store.call("phonesync:set-destination", JSONArray().put(settings(folder = "../x")))
}
assertEquals("Backups", store.get()!!.folder)
}
}
|