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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
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.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test
import org.meshbay.client.notify.Notifier
import org.meshbay.client.notify.PushState
class PushTest {
private fun payload(vararg pairs: Pair<String, Any?>) =
JSONObject().apply { put("v", 1); pairs.forEach { (k, v) -> put(k, v ?: JSONObject.NULL) } }
.toString().toByteArray()
@Test fun `a conversation is one entry per group, anything else one per notification`() {
val chat = Notifier.parse(payload("id" to 7, "kind" to "chat_message", "title" to "a posted in b",
"group_id" to "g-1", "link" to "#/group/g-1"))!!
assertEquals(Notifier.CHANNEL_CHAT, chat.channel)
assertEquals("chat:g-1", chat.tag)
assertEquals("#/group/g-1", chat.link)
val invite = Notifier.parse(payload("id" to 8, "kind" to "group_invite", "title" to "x invited you",
"group_id" to null, "link" to "#/"))!!
assertEquals(Notifier.CHANNEL_OTHER, invite.channel)
assertEquals("n:8", invite.tag)
}
@Test fun `a link that is not a route inside the page is dropped, not followed`() {
for (bad in listOf("https://elsewhere.example/", "javascript:alert(1)", "#/x\";alert(1)//", "//host/#/")) {
val shown = Notifier.parse(payload("id" to 1, "kind" to "k", "title" to "t", "link" to bad))!!
assertNull(bad, shown.link)
}
}
@Test fun `what is not ours to draw is not drawn`() {
assertNull(Notifier.parse("not json".toByteArray()))
assertNull(Notifier.parse(JSONObject().put("v", 2).put("title", "t").toString().toByteArray()))
assertNull(Notifier.parse(payload("id" to 1, "kind" to "k", "title" to " ")))
}
private val sub = "0f8fad5b-d9cb-469f-a165-70867728950e"
private val account = "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
private val secret = "Zm9vYmFyYmF6cXV4X3NlY3JldF92YWx1ZQ"
private val since = "2026-10-09T10:00:00.123456+00:00"
@Test fun `the hub is registered again when the distributor hands out a new endpoint`() {
val state = PushState(FakePrefs())
state.endpoint("https://push.example.net/1", "k", "a") // not asked for: ignored
assertEquals(PushState.OFF, state.status().getString("state"))
state.requested()
state.endpoint("https://push.example.net/1", "k", "a")
state.remember(sub, account, secret, since)
val s = state.status()
assertEquals(s.getString("endpoint"), s.getString("registered"))
state.endpoint("https://push.example.net/2", "k", "a")
val moved = state.status()
assertEquals("https://push.example.net/1", moved.getString("registered"))
assertEquals("https://push.example.net/2", moved.getString("endpoint"))
}
@Test fun `no distributor, or one that gives no keys, means fetching and not failing`() {
val state = PushState(FakePrefs())
state.requested()
state.endpoint("https://push.example.net/1", null, null)
val s = state.status()
assertEquals(PushState.READY, s.getString("state"))
assertEquals("NO_KEYS", s.getString("reason"))
assertTrue(s.isNull("endpoint"))
state.remember(sub, account, secret, since)
assertEquals(PushState.PollTarget(sub, secret, since), state.pollTarget())
}
@Test fun `the fetch cursor only moves forward`() {
val state = PushState(FakePrefs())
state.requested()
state.remember(sub, account, secret, since)
state.advance("2026-10-09T09:00:00+00:00")
assertEquals(since, state.pollTarget()!!.since)
state.advance("2026-10-09T11:00:00+00:00")
assertEquals("2026-10-09T11:00:00+00:00", state.pollTarget()!!.since)
}
@Test fun `a line pushed then fetched is drawn once, and a conversation moving on is news`() {
val state = PushState(FakePrefs())
assertTrue(state.firstSight(7, "2026-10-09T10:00:00+00:00"))
assertFalse(state.firstSight(7, "2026-10-09T10:00:00+00:00"))
assertTrue(state.firstSight(7, "2026-10-09T10:05:00+00:00"))
assertFalse(state.firstSight(7, "2026-10-09T10:01:00+00:00"))
}
@Test fun `a row the hub forgot stops the fetch until the page registers again`() {
val state = PushState(FakePrefs())
state.requested()
state.remember(sub, account, secret, since)
state.forgetSubscription()
assertNull(state.pollTarget())
}
@Test fun `only ids are remembered`() {
val state = PushState(FakePrefs())
assertThrows(IllegalArgumentException::class.java) { state.remember("../x", account, secret, since) }
assertThrows(IllegalArgumentException::class.java) { state.remember(sub, "", secret, since) }
assertThrows(IllegalArgumentException::class.java) { state.remember(sub, account, "short", since) }
assertThrows(IllegalArgumentException::class.java) { state.remember(sub, account, secret, "yesterday") }
}
}
|