mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
test(clink): regression tests for the audit fixes
Locks in the protocol-layer fixes that were previously only compile-checked: - offerLargePriceRoundTripIsUnsigned: a price > Int.MAX_VALUE round-trips as a positive Long (guards the unsigned-decode fix). - cannotDecryptAuthoredEventMissingRecipient: an authored event with no p tag can't be decrypted by anyone (guards the no-self-fallback conversationPeer). - manageCreateRequestSerializesNested + manageFailureResponseParsesField: the Manage request nests under offer.fields, payer_data is a string list, and the failure response carries field (guards the 21003 shape fix). All CLINK tests pass.
This commit is contained in:
+49
@@ -21,6 +21,10 @@
|
||||
package com.vitorpamplona.quartz.experimental.clink
|
||||
|
||||
import com.vitorpamplona.quartz.experimental.clink.debits.DebitResponse
|
||||
import com.vitorpamplona.quartz.experimental.clink.manage.ManageOffer
|
||||
import com.vitorpamplona.quartz.experimental.clink.manage.ManageRequest
|
||||
import com.vitorpamplona.quartz.experimental.clink.manage.ManageResponse
|
||||
import com.vitorpamplona.quartz.experimental.clink.manage.OfferFields
|
||||
import com.vitorpamplona.quartz.experimental.clink.offers.OfferEvent
|
||||
import com.vitorpamplona.quartz.experimental.clink.offers.OfferRequest
|
||||
import com.vitorpamplona.quartz.experimental.clink.offers.OfferResponse
|
||||
@@ -88,6 +92,23 @@ class ClinkEventTest {
|
||||
assertFalse(request.canDecrypt(stranger))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotDecryptAuthoredEventMissingRecipient() {
|
||||
// A malformed event I authored but with no `p` tag: my own key must NOT be used as
|
||||
// the conversation peer (no self-fallback), so neither party can derive a key.
|
||||
val noRecipient =
|
||||
OfferEvent(
|
||||
id = "a".repeat(64),
|
||||
pubKey = payer.pubKey,
|
||||
createdAt = 1L,
|
||||
tags = arrayOf(ClinkVersionTag.assemble()),
|
||||
content = "encrypted-placeholder",
|
||||
sig = "b".repeat(128),
|
||||
)
|
||||
assertFalse(noRecipient.canDecrypt(payer))
|
||||
assertFalse(noRecipient.canDecrypt(service))
|
||||
}
|
||||
|
||||
// --- JSON DTOs ---
|
||||
|
||||
@Test
|
||||
@@ -136,4 +157,32 @@ class ClinkEventTest {
|
||||
assertTrue(parsed.isOk())
|
||||
assertEquals("deadbeef", parsed.preimage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun manageCreateRequestSerializesNested() {
|
||||
val request =
|
||||
ManageRequest(
|
||||
resource = ManageRequest.RESOURCE_OFFER,
|
||||
action = ManageRequest.ACTION_CREATE,
|
||||
offer = ManageOffer(fields = OfferFields("Coffee", 1500L, "https://x/cb", listOf("email", "name"))),
|
||||
)
|
||||
val json = OptimizedJsonMapper.toJson(request)
|
||||
|
||||
// Offer data is nested under offer.fields (not flat).
|
||||
assertTrue(json.contains("\"offer\""), json)
|
||||
assertTrue(json.contains("\"fields\""), json)
|
||||
|
||||
val parsed = OptimizedJsonMapper.fromJsonTo<ManageRequest>(json)
|
||||
assertEquals("Coffee", parsed.offer?.fields?.label)
|
||||
assertEquals(1500L, parsed.offer?.fields?.price_sats)
|
||||
// payer_data is a list of field names, not a map.
|
||||
assertEquals(listOf("email", "name"), parsed.offer?.fields?.payer_data)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun manageFailureResponseParsesField() {
|
||||
val parsed = OptimizedJsonMapper.fromJsonTo<ManageResponse>("""{"res":"GFY","code":5,"error":"bad","field":"price_sats"}""")
|
||||
assertFalse(parsed.isOk())
|
||||
assertEquals("price_sats", parsed.field)
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -52,6 +52,19 @@ class ClinkPointerTest {
|
||||
assertEquals(offer, parsed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun offerLargePriceRoundTripIsUnsigned() {
|
||||
// A price with the high bit set (> Int.MAX_VALUE) must round-trip as a positive
|
||||
// Long — the price is an unsigned 4-byte big-endian integer, so reading it signed
|
||||
// would wrap it negative.
|
||||
val price = 3_000_000_000L
|
||||
val offer = NOffer(pubKey, listOf(relay), null, OfferPriceType.FIXED, price)
|
||||
val parsed = ClinkPointerParser.parse(offer.encode()) as NOffer
|
||||
|
||||
assertEquals(price, parsed.price)
|
||||
assertEquals(offer, parsed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun debitStaticRoundTrip() {
|
||||
val debit = NDebit(pubKey, listOf(relay), "pointer-7", null)
|
||||
|
||||
Reference in New Issue
Block a user