compat: fix the too-new message's grammar, add a live gate test
The generated refusal read "point at a app within range". Also adds LiveCompatTest, which checks the half a unit test cannot reach: that two independently-built artifacts agree on the window, that the profile stays readable for a version the server refuses, and that both bounds are enforced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0c5b021b63
commit
9d6572bc33
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package app.echo_lot.engine
|
||||
|
||||
import app.echo_lot.protocol.Compat
|
||||
import app.echo_lot.protocol.ControlClient
|
||||
import app.echo_lot.protocol.VersionRefused
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
/**
|
||||
* Checks the version gate against a LIVE server — the half that unit tests cannot reach, because
|
||||
* the whole point is that two independently-built artifacts agree. Self-skips without
|
||||
* ECHOLOT_LIVE_*.
|
||||
*/
|
||||
class LiveCompatTest {
|
||||
|
||||
private val url = System.getenv("ECHOLOT_LIVE_URL")
|
||||
private val pin = System.getenv("ECHOLOT_LIVE_PIN")
|
||||
private val cred = System.getenv("ECHOLOT_LIVE_CRED")
|
||||
|
||||
private fun clientAs(version: String) = ControlClient(url!!, setOf(pin!!), version)
|
||||
|
||||
@Test
|
||||
fun theServerAdvertisesAndEnforcesItsWindow() {
|
||||
if (url == null || pin == null || cred == null) {
|
||||
println("LiveCompatTest skipped (no ECHOLOT_LIVE_* env)"); return
|
||||
}
|
||||
|
||||
// The profile must state the window — without it the app cannot pre-empt a refusal.
|
||||
val profile = clientAs("0.2.0").profile(cred)
|
||||
println("server ${profile.serverVersion} protocol=${profile.compat.protocolVersion} " +
|
||||
"accepts app [${profile.compat.appMin}, ${profile.compat.appMax})")
|
||||
assertTrue(profile.compat.protocolVersion.isNotBlank(), "profile omits protocol_version")
|
||||
assertTrue(profile.compat.appMin.isNotBlank(), "profile omits app_min")
|
||||
|
||||
// This build must be inside it, or every other live test here is meaningless.
|
||||
val verdict = Compat.check(profile, "0.2.0")
|
||||
assertEquals(Compat.Verdict.OK, verdict.verdict, verdict.message ?: "")
|
||||
|
||||
// The profile stays reachable for a version the server would otherwise refuse: that is
|
||||
// how a refused client discovers what it needs.
|
||||
val ancient = clientAs("0.1.0")
|
||||
val stillReadable = ancient.profile(cred)
|
||||
assertEquals(profile.serverVersion, stillReadable.serverVersion,
|
||||
"the profile endpoint must never be gated on app version")
|
||||
|
||||
// And a gated endpoint refuses it, with a message naming the window.
|
||||
try {
|
||||
ancient.createSession(cred, System.getenv("ECHOLOT_LIVE_TARGET") ?: "fmr")
|
||||
fail("server accepted a session from an out-of-window app")
|
||||
} catch (e: VersionRefused) {
|
||||
val msg = assertNotNull(e.message)
|
||||
println("refused as expected: $msg")
|
||||
assertTrue(msg.contains("0.1.0"), "refusal should name the offending version: $msg")
|
||||
assertTrue(msg.contains(profile.compat.appMin), "refusal should name the window: $msg")
|
||||
}
|
||||
|
||||
// Too new is refused the same way — the window is a range, not a floor.
|
||||
try {
|
||||
clientAs("99.0.0").createSession(cred, System.getenv("ECHOLOT_LIVE_TARGET") ?: "fmr")
|
||||
fail("server accepted a session from an app above its window")
|
||||
} catch (e: VersionRefused) {
|
||||
println("too-new refused as expected: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user