0.26, and a test for the wire the relay and this client agree on
Both halves of 8.9c were written in different repositories on the same afternoon, and the way that goes wrong is a field name: this client posts "digests" and the relay reads "tags", both suites stay green, and presence silently never works. RosterTagWireTest asserts the exact bytes against a mock server, including the route paths and the token-keyed answer shape. It also pins two behaviours worth keeping: no tokens means no request at all, because a relay asked about nothing still costs a round trip and a rate-limit unit, and a token the relay does not recognise comes back offline rather than missing, since "offline" and "no answer" are different things to whatever merges this with a roster. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7ebd519942
commit
89474a1fc7
@@ -43,8 +43,8 @@ android {
|
||||
// is a label. Code 24 with name 0.24 closes it by skipping the name
|
||||
// 0.23, which costs nothing because a name is a label and may skip.
|
||||
// From here they move together and match: 25 with 0.25, 26 with 0.26.
|
||||
versionCode = 25
|
||||
versionName = "0.25"
|
||||
versionCode = 26
|
||||
versionName = "0.26"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.flamenet.client
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.json.JSONObject
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* §8.9c, the wire between this client and the relay.
|
||||
*
|
||||
* Both sides of this pair were written in different repositories on the same
|
||||
* afternoon, and the way that goes wrong is a field name: the client posts
|
||||
* `digests` and the relay reads `tags`, both suites stay green, and presence
|
||||
* silently never works. This asserts the exact bytes.
|
||||
*
|
||||
* The literals here are the relay's, from `PublishRosterTagsRequest` and
|
||||
* `PresenceByTokenResponse` in `Wire.swift`. Changing one without the other is
|
||||
* what this is for.
|
||||
*/
|
||||
class RosterTagWireTest {
|
||||
private lateinit var server: MockWebServer
|
||||
|
||||
@BeforeTest fun start() {
|
||||
server = MockWebServer().also { it.start() }
|
||||
}
|
||||
|
||||
@AfterTest fun stop() {
|
||||
server.shutdown()
|
||||
}
|
||||
|
||||
private fun api() = MessengerApi(server.url("/").toString().trimEnd('/'), "ada", "secret")
|
||||
|
||||
private fun ok(body: String) =
|
||||
MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(body)
|
||||
|
||||
@Test
|
||||
fun `publishing posts digests to the route the relay mounts`() {
|
||||
server.enqueue(ok("""{"ok":true}"""))
|
||||
api().publishRosterTags(listOf("AAA=", "BBB="))
|
||||
|
||||
val request = server.takeRequest()
|
||||
assertEquals("POST", request.method)
|
||||
assertTrue(
|
||||
request.path!!.endsWith("/messenger/roster/tags"),
|
||||
"posted to ${request.path}",
|
||||
)
|
||||
val body = JSONObject(request.body.readUtf8())
|
||||
// `digests`, the relay's own field name. Not `tags`, which is what the
|
||||
// group route next door calls its equivalent.
|
||||
val digests = body.getJSONArray("digests")
|
||||
assertEquals(2, digests.length())
|
||||
assertEquals("AAA=", digests.getString(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `asking for presence posts tokens and reads the answer back by token`() {
|
||||
server.enqueue(
|
||||
ok(
|
||||
"""{"presence":[
|
||||
{"token":"TOKEN-A","presence":"online"},
|
||||
{"token":"TOKEN-B","presence":"offline"}
|
||||
]}""",
|
||||
),
|
||||
)
|
||||
val answers = api().presenceByToken(listOf("TOKEN-A", "TOKEN-B"))
|
||||
|
||||
val request = server.takeRequest()
|
||||
assertEquals("POST", request.method)
|
||||
assertTrue(request.path!!.endsWith("/messenger/presence"), "posted to ${request.path}")
|
||||
assertEquals(
|
||||
"TOKEN-A",
|
||||
JSONObject(request.body.readUtf8()).getJSONArray("tokens").getString(0),
|
||||
)
|
||||
// Keyed by the token that asked, because the relay names nobody.
|
||||
assertEquals("online", answers["TOKEN-A"])
|
||||
assertEquals("offline", answers["TOKEN-B"])
|
||||
}
|
||||
|
||||
/**
|
||||
* No request at all rather than one with an empty array. A relay asked
|
||||
* about nothing still costs a round trip and a rate-limit unit.
|
||||
*/
|
||||
@Test
|
||||
fun `no tokens means no request`() {
|
||||
assertEquals(emptyMap(), api().presenceByToken(emptyList()))
|
||||
assertEquals(0, server.requestCount)
|
||||
}
|
||||
|
||||
/**
|
||||
* A row for a token the relay did not recognise reads as offline, and the
|
||||
* client must carry that through rather than drop the key: "offline" and
|
||||
* "no answer" are different things to whatever merges this with a roster.
|
||||
*/
|
||||
@Test
|
||||
fun `an unrecognised token comes back offline, not missing`() {
|
||||
server.enqueue(ok("""{"presence":[{"token":"NOPE","presence":"offline"}]}"""))
|
||||
assertEquals("offline", api().presenceByToken(listOf("NOPE"))["NOPE"])
|
||||
}
|
||||
|
||||
/** An older relay has neither route, and that must not throw into the UI. */
|
||||
@Test
|
||||
fun `a relay without the route is not fatal`() {
|
||||
server.enqueue(MockResponse().setResponseCode(404).setBody("""{"error":true}"""))
|
||||
val failed = runCatching { api().presenceByToken(listOf("TOKEN-A")) }
|
||||
assertTrue(failed.isFailure, "the API surfaces it; Session is what swallows it")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user