Fixes some compilation issues

This commit is contained in:
Vitor Pamplona
2026-03-10 16:14:38 -04:00
parent c0e97c19c0
commit 087e5878a6
2 changed files with 1 additions and 269 deletions
@@ -52,7 +52,7 @@ class NewMessageTaggerKeyParseTest {
com.vitorpamplona.amethyst.model
.Note(hex)
override suspend fun getOrCreateAddressableNote(address: Address) =
override fun getOrCreateAddressableNote(address: Address) =
com.vitorpamplona.amethyst.model
.AddressableNote(address)
}
@@ -1,268 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.service.followimport
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinResolveOutcome
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class FollowListImporterTest {
private val importer = FollowListImporter()
// Importer with a Namecoin resolver that always returns ServersUnreachable
// (simulates no real ElectrumX servers in test env)
private val importerWithNamecoin =
FollowListImporter(
resolveNamecoin = { identifier ->
NamecoinResolveOutcome.ServersUnreachable("Test: no servers available")
},
)
// ── Hex pubkey resolution ──────────────────────────────────────────
@Test
fun `resolves 64-char hex pubkey`() =
runBlocking {
val hex = "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"
val r = importer.resolveIdentifier(hex)
assertNotNull(r)
assertEquals(hex, r!!.pubkeyHex)
assertNull(r.namecoinSource)
}
@Test
fun `lowercases hex pubkey`() =
runBlocking {
val hex = "B0635D6A9851D3AED0CD6C495B282167ACF761729078D975FC341B22650B07B9"
assertEquals(hex.lowercase(), importer.resolveIdentifier(hex)!!.pubkeyHex)
}
@Test
fun `rejects short hex`() =
runBlocking {
assertNull(importer.resolveIdentifier("abcdef"))
}
// ── npub resolution ────────────────────────────────────────────────
@Test
fun `resolves valid npub`() =
runBlocking {
val npub = "npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6"
val r = importer.resolveIdentifier(npub)
assertNotNull(r)
assertEquals(64, r!!.pubkeyHex.length)
assertTrue(r.pubkeyHex.matches(Regex("^[0-9a-f]{64}$")))
assertNull(r.namecoinSource)
}
@Test
fun `rejects invalid npub`() =
runBlocking {
assertNull(importer.resolveIdentifier("npub1invalid"))
}
// ── NIP-05 resolution ──────────────────────────────────────────────
@Test
fun `delegates NIP-05 to callback`() =
runBlocking {
val expected = "aaaa000000000000000000000000000000000000000000000000000000000001"
val r = importer.resolveIdentifier("[email protected]", resolveNip05 = { expected })
assertNotNull(r)
assertEquals(expected, r!!.pubkeyHex)
assertNull(r.namecoinSource)
}
@Test
fun `returns null for NIP-05 without resolver`() =
runBlocking {
assertNull(importer.resolveIdentifier("[email protected]"))
}
// ── nsec rejection ─────────────────────────────────────────────────
@Test
fun `rejects nsec private keys`() =
runBlocking {
assertNull(
importer.resolveIdentifier(
"nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
resolveNip05 = { "should-not-be-called" },
),
)
}
// ── Namecoin identifier detection ──────────────────────────────────
@Test
fun `identifies dot-bit as Namecoin`() {
assertTrue(NamecoinNameResolver.isNamecoinIdentifier("example.bit"))
assertTrue(NamecoinNameResolver.isNamecoinIdentifier("alice@example.bit"))
assertTrue(NamecoinNameResolver.isNamecoinIdentifier("_@example.bit"))
}
@Test
fun `identifies d-slash as Namecoin`() {
assertTrue(NamecoinNameResolver.isNamecoinIdentifier("d/example"))
}
@Test
fun `identifies id-slash as Namecoin`() {
assertTrue(NamecoinNameResolver.isNamecoinIdentifier("id/alice"))
}
@Test
fun `rejects non-Namecoin identifiers`() {
assertFalse(NamecoinNameResolver.isNamecoinIdentifier("[email protected]"))
assertFalse(NamecoinNameResolver.isNamecoinIdentifier("npub1abc"))
assertFalse(NamecoinNameResolver.isNamecoinIdentifier(""))
}
// ── Kind 3 parsing ─────────────────────────────────────────────────
@Test
fun `parses kind 3 p-tags with relay hints and petnames`() =
runBlocking {
val target = "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"
val followA = "aaaa000000000000000000000000000000000000000000000000000000000001"
val followB = "bbbb000000000000000000000000000000000000000000000000000000000002"
val result =
importer.fetchFollowList(
identifier = target,
relayUrls = listOf("wss://test"),
fetchEvent = { kind, author, _, onEvent ->
assertEquals(3, kind)
assertEquals(target, author)
onEvent(
Kind3EventData(
pTags =
listOf(
listOf(followA, "wss://relay.example.com", "alice"),
listOf(followB, "", "bob"),
),
createdAt = 1700000000L,
),
)
AutoCloseable {}
},
)
assertTrue(result is FollowListResult.Success)
val s = result as FollowListResult.Success
assertEquals(2, s.follows.size)
assertEquals(followA, s.follows[0].pubkeyHex)
assertEquals("wss://relay.example.com", s.follows[0].relayHint)
assertEquals("alice", s.follows[0].petname)
assertEquals(followB, s.follows[1].pubkeyHex)
assertNull(s.follows[1].relayHint)
assertEquals("bob", s.follows[1].petname)
assertNull(s.resolvedViaNamecoin)
}
@Test
fun `deduplicates follows`() =
runBlocking {
val pk = "aaaa000000000000000000000000000000000000000000000000000000000001"
val result =
importer.fetchFollowList(
identifier = pk,
relayUrls = listOf("wss://t"),
fetchEvent = { _, _, _, onEvent ->
onEvent(Kind3EventData(pTags = listOf(listOf(pk), listOf(pk)), createdAt = 1L))
AutoCloseable {}
},
)
assertEquals(1, (result as FollowListResult.Success).follows.size)
}
@Test
fun `skips invalid pubkeys in p-tags`() =
runBlocking {
val result =
importer.fetchFollowList(
identifier = "aaaa000000000000000000000000000000000000000000000000000000000001",
relayUrls = listOf("wss://t"),
fetchEvent = { _, _, _, onEvent ->
onEvent(
Kind3EventData(
pTags = listOf(listOf("tooshort"), listOf(""), listOf("zzzz" + "0".repeat(60))),
createdAt = 1L,
),
)
AutoCloseable {}
},
)
assertEquals(0, (result as FollowListResult.Success).follows.size)
}
@Test
fun `returns NoFollowList on timeout`() =
runBlocking {
val result =
importer.fetchFollowList(
identifier = "aaaa000000000000000000000000000000000000000000000000000000000001",
relayUrls = listOf("wss://t"),
fetchEvent = { _, _, _, _ -> AutoCloseable {} },
timeoutMs = 200,
)
assertTrue(result is FollowListResult.NoFollowList)
}
@Test
fun `returns Error on fetch exception`() =
runBlocking {
val result =
importer.fetchFollowList(
identifier = "aaaa000000000000000000000000000000000000000000000000000000000001",
relayUrls = listOf("wss://t"),
fetchEvent = { _, _, _, _ -> throw RuntimeException("Connection refused") },
)
assertTrue(result is FollowListResult.Error)
assertTrue((result as FollowListResult.Error).message.contains("Connection refused"))
}
// ── Namecoin-specific error messages ───────────────────────────────
@Test
fun `gives Namecoin-specific error for dot-bit failure`() =
runBlocking {
// Namecoin resolution will fail because NamecoinNameService is not
// configured with real servers in a test environment. The importer
// should give a Namecoin-specific error message.
val result =
importerWithNamecoin.fetchFollowList(
identifier = "nonexistent.bit",
relayUrls = listOf("wss://test"),
fetchEvent = { _, _, _, _ -> AutoCloseable {} },
timeoutMs = 500,
)
assertTrue(result is FollowListResult.InvalidIdentifier)
assertTrue((result as FollowListResult.InvalidIdentifier).reason.contains("Namecoin"))
}
}