refactor(geode): test fixtures + collectUntilEose helper, move to testFixtures sourceSet
The in-process relay was usable but every consumer test paid a 10–15
line tax for scope/client setup, manual cleanup inside the test body
(leaks on assertion failure), hardcoded "ws://127.0.0.1:7770/" magic
strings, and an inline collectUntilEose pattern reinvented per file.
Net: ~250 LOC removed, every test gets correct @After cleanup, and
the synthetic event builders no longer ship in geode's production jar.
- Move geode.fixtures (synthetic event builders) and the new
geode.testing package from src/main to a new src/testFixtures
source set via the java-test-fixtures plugin. Production geode jar
no longer includes them; consumers wire them with
testImplementation(testFixtures(project(":geode"))).
- Add geode.testing.RelayClientTest open class — owns hub, scope,
client, defaultRelay, defaultRelayUrl with @After-driven cleanup.
- Add geode.testing.collectUntilEose / collectUntilEoseMulti
NostrClient extensions with a shared subId counter and timeout knob.
Replaces hand-rolled subscriber loops in 4+ files.
- Add RelayHub.DEFAULT_URL constant so tests stop typing
"ws://127.0.0.1:7770/" inline.
- Migrate the 8 quartz NostrClient*Test files + geode's
Nip01ComplianceTest to extend RelayClientTest and (where REQ/EOSE
is the pattern) call collectUntilEose. Delete the redundant
BaseNostrClientTest wrapper.
This commit is contained in:
+13
-2
@@ -183,8 +183,10 @@ kotlin {
|
||||
|
||||
// In-process Nostr relay (geode) so JVM/Android host
|
||||
// tests don't need network access or a Rust toolchain.
|
||||
// The `geode.fixtures` package carries the test-only
|
||||
// event generators and corpus loader.
|
||||
// testFixtures (RelayClientTest base, fixtures,
|
||||
// collectUntilEose) are wired below at the top-level
|
||||
// `dependencies` block — the KMP source-set DSL
|
||||
// doesn't expose the `testFixtures(...)` consumer.
|
||||
implementation(project(":geode"))
|
||||
}
|
||||
}
|
||||
@@ -347,6 +349,15 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
// testFixtures(...) consumer lives outside the KMP source-set DSL —
|
||||
// the KMP source-set `dependencies { }` block uses
|
||||
// `KotlinDependencyHandler`, which does not expose the
|
||||
// `testFixtures(...)` projection. The standard Gradle dependency
|
||||
// configuration name (`jvmAndroidTestImplementation`) does work here.
|
||||
dependencies {
|
||||
"jvmAndroidTestImplementation"(testFixtures(project(":geode")))
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
// sources publishing is always enabled by the Kotlin Multiplatform plugin
|
||||
configure(
|
||||
|
||||
-40
@@ -1,40 +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.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.RelayHub
|
||||
|
||||
/**
|
||||
* Base for tests that drive a real `NostrClient` against an in-process Nostr
|
||||
* relay. Each subclass instance gets its own [RelayHub] so tests can
|
||||
* preload events and assert deterministic counts without hitting the
|
||||
* network or relying on production relays.
|
||||
*
|
||||
* To replace with the previous behaviour (real OkHttp WebSocket against
|
||||
* `wss://nos.lol`), instantiate `BasicOkHttpWebSocket.Builder` directly in
|
||||
* the specific test that needs it.
|
||||
*/
|
||||
open class BaseNostrClientTest {
|
||||
val relayHub: RelayHub = RelayHub()
|
||||
|
||||
/** Plug into `NostrClient(socketBuilder, scope)`. */
|
||||
val socketBuilder get() = relayHub
|
||||
}
|
||||
+5
-21
@@ -20,25 +20,20 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.fetchFirst
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientFirstEventTest : BaseNostrClientTest() {
|
||||
class NostrClientFirstEventTest : RelayClientTest() {
|
||||
@Test
|
||||
fun testDownloadFirstEvent() =
|
||||
runBlocking {
|
||||
val pubKey = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"
|
||||
val relayUrl = "ws://127.0.0.1:7770/"
|
||||
|
||||
val seed =
|
||||
Event(
|
||||
@@ -50,25 +45,14 @@ class NostrClientFirstEventTest : BaseNostrClientTest() {
|
||||
content = """{"name":"vitor"}""",
|
||||
sig = "b".repeat(128),
|
||||
)
|
||||
relayHub.getOrCreate(relayUrl).preload(seed)
|
||||
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(seed)
|
||||
|
||||
val event =
|
||||
client.fetchFirst(
|
||||
relay = relayUrl,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOf(pubKey),
|
||||
),
|
||||
relay = defaultRelayUrl,
|
||||
filter = Filter(kinds = listOf(MetadataEvent.KIND), authors = listOf(pubKey)),
|
||||
)
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
|
||||
assertEquals(MetadataEvent.KIND, event?.kind)
|
||||
assertEquals(pubKey, event?.pubKey)
|
||||
}
|
||||
|
||||
+9
-31
@@ -19,18 +19,14 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -38,17 +34,11 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientManualSubTest : BaseNostrClientTest() {
|
||||
class NostrClientManualSubTest : RelayClientTest() {
|
||||
@Test
|
||||
fun testEoseAfter100Events() =
|
||||
runBlocking {
|
||||
val relayUrl = RelayUrlNormalizer.normalize("ws://127.0.0.1:7770/")
|
||||
relayHub
|
||||
.getOrCreate(relayUrl)
|
||||
.preload(SyntheticEvents.batch(150, kind = MetadataEvent.KIND))
|
||||
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(SyntheticEvents.batch(150, kind = MetadataEvent.KIND))
|
||||
|
||||
val resultChannel = Channel<String>(UNLIMITED)
|
||||
val events = mutableListOf<String>()
|
||||
@@ -73,18 +63,11 @@ class NostrClientManualSubTest : BaseNostrClientTest() {
|
||||
}
|
||||
}
|
||||
|
||||
val filters =
|
||||
mapOf(
|
||||
relayUrl to
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
client.subscribe(mySubId, filters, listener)
|
||||
client.subscribe(
|
||||
mySubId,
|
||||
mapOf(defaultRelayUrl to listOf(Filter(kinds = listOf(MetadataEvent.KIND), limit = 100))),
|
||||
listener,
|
||||
)
|
||||
|
||||
withTimeoutOrNull(10000) {
|
||||
while (events.size < 101) {
|
||||
@@ -94,12 +77,7 @@ class NostrClientManualSubTest : BaseNostrClientTest() {
|
||||
}
|
||||
|
||||
resultChannel.close()
|
||||
|
||||
client.unsubscribe(mySubId)
|
||||
client.disconnect()
|
||||
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
|
||||
assertEquals(101, events.size)
|
||||
assertEquals(true, events.take(100).all { it.length == 64 })
|
||||
|
||||
+6
-33
@@ -21,19 +21,15 @@
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.count
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientQueryCountTest : BaseNostrClientTest() {
|
||||
class NostrClientQueryCountTest : RelayClientTest() {
|
||||
private val relayA = "ws://127.0.0.1:7771/".normalizeRelayUrl()
|
||||
private val relayB = "ws://127.0.0.1:7772/".normalizeRelayUrl()
|
||||
|
||||
@@ -44,16 +40,16 @@ class NostrClientQueryCountTest : BaseNostrClientTest() {
|
||||
// 5 metadata + 3 outbox relay events on A, 2 metadata + 7 outbox on B.
|
||||
// Each event needs a distinct (kind, pubkey, dTag) to avoid replaceable-event collisions.
|
||||
fun pk(seed: Int) = SyntheticEvents.hexId(seed)
|
||||
relayHub.getOrCreate(relayA).preload(
|
||||
hub.getOrCreate(relayA).preload(
|
||||
(1..5).map { SyntheticEvents.fakeEvent(idSeed = it, kind = 0, pubKey = pk(it)) },
|
||||
)
|
||||
relayHub.getOrCreate(relayA).preload(
|
||||
hub.getOrCreate(relayA).preload(
|
||||
(1..3).map { SyntheticEvents.fakeEvent(idSeed = 1000 + it, kind = 10002, pubKey = pk(1000 + it)) },
|
||||
)
|
||||
relayHub.getOrCreate(relayB).preload(
|
||||
hub.getOrCreate(relayB).preload(
|
||||
(1..2).map { SyntheticEvents.fakeEvent(idSeed = 2000 + it, kind = 0, pubKey = pk(2000 + it)) },
|
||||
)
|
||||
relayHub.getOrCreate(relayB).preload(
|
||||
hub.getOrCreate(relayB).preload(
|
||||
(1..7).map { SyntheticEvents.fakeEvent(idSeed = 3000 + it, kind = 10002, pubKey = pk(3000 + it)) },
|
||||
)
|
||||
}
|
||||
@@ -62,41 +58,22 @@ class NostrClientQueryCountTest : BaseNostrClientTest() {
|
||||
fun testQueryCountSuspend() =
|
||||
runBlocking {
|
||||
seed()
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
|
||||
val result = client.count(relayA, metadata)
|
||||
|
||||
assertEquals(5, result?.count)
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQueryCountSuspendAllEvents() =
|
||||
runBlocking {
|
||||
seed()
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
|
||||
val result = client.count(relayA, Filter())
|
||||
|
||||
assertEquals(8, result?.count)
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQueryCountSuspendMultipleRelays() =
|
||||
runBlocking {
|
||||
seed()
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
|
||||
val results =
|
||||
client.count(
|
||||
mapOf(
|
||||
@@ -107,9 +84,5 @@ class NostrClientQueryCountTest : BaseNostrClientTest() {
|
||||
|
||||
assertEquals(8, results[relayA]?.count)
|
||||
assertEquals(9, results[relayB]?.count)
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
}
|
||||
}
|
||||
|
||||
+8
-54
@@ -19,22 +19,18 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EoseMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
@@ -44,12 +40,12 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientRepeatSubTest : BaseNostrClientTest() {
|
||||
class NostrClientRepeatSubTest : RelayClientTest() {
|
||||
@Test
|
||||
fun testRepeatSubEvents() =
|
||||
runBlocking {
|
||||
// Each replaceable kind needs unique pubkeys.
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
defaultRelay.preload(
|
||||
(1..150).map {
|
||||
SyntheticEvents.fakeEvent(
|
||||
idSeed = it,
|
||||
@@ -58,7 +54,7 @@ class NostrClientRepeatSubTest : BaseNostrClientTest() {
|
||||
)
|
||||
},
|
||||
)
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
defaultRelay.preload(
|
||||
(1..50).map {
|
||||
SyntheticEvents.fakeEvent(
|
||||
idSeed = 100_000 + it,
|
||||
@@ -68,9 +64,6 @@ class NostrClientRepeatSubTest : BaseNostrClientTest() {
|
||||
},
|
||||
)
|
||||
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
|
||||
val resultChannel = Channel<String>(UNLIMITED)
|
||||
val events = mutableListOf<String>()
|
||||
val mySubId = "test-sub-id-2"
|
||||
@@ -101,38 +94,11 @@ class NostrClientRepeatSubTest : BaseNostrClientTest() {
|
||||
|
||||
client.addConnectionListener(listener)
|
||||
|
||||
val filters =
|
||||
mapOf(
|
||||
RelayUrlNormalizer.normalize("ws://127.0.0.1:7770/") to
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val filters = mapOf(defaultRelayUrl to listOf(Filter(kinds = listOf(MetadataEvent.KIND), limit = 100)))
|
||||
val filtersShouldIgnore =
|
||||
mapOf(
|
||||
RelayUrlNormalizer.normalize("ws://127.0.0.1:7770/") to
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(AdvertisedRelayListEvent.KIND),
|
||||
limit = 500,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
mapOf(defaultRelayUrl to listOf(Filter(kinds = listOf(AdvertisedRelayListEvent.KIND), limit = 500)))
|
||||
val filtersShouldSendAfterEOSE =
|
||||
mapOf(
|
||||
RelayUrlNormalizer.normalize("ws://127.0.0.1:7770/") to
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(AdvertisedRelayListEvent.KIND),
|
||||
limit = 10,
|
||||
),
|
||||
),
|
||||
)
|
||||
mapOf(defaultRelayUrl to listOf(Filter(kinds = listOf(AdvertisedRelayListEvent.KIND), limit = 10)))
|
||||
|
||||
coroutineScope {
|
||||
launch {
|
||||
@@ -161,30 +127,18 @@ class NostrClientRepeatSubTest : BaseNostrClientTest() {
|
||||
|
||||
client.unsubscribe(mySubId)
|
||||
client.removeConnectionListener(listener)
|
||||
client.disconnect()
|
||||
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
|
||||
// The relay may return up to limit events before EOSE; some relays return
|
||||
// one extra past the requested limit, so don't assert on the exact count.
|
||||
// First sub: <= 100 metadata events, then EOSE.
|
||||
// Second sub: <= 10 advertised relay list events, then EOSE.
|
||||
val firstEose = events.indexOf("EOSE")
|
||||
val lastEose = events.lastIndexOf("EOSE")
|
||||
|
||||
// both EOSEs must be present and distinct
|
||||
assertEquals(true, firstEose >= 0)
|
||||
assertEquals(true, lastEose > firstEose)
|
||||
// last entry is the second EOSE (loop stops on it)
|
||||
assertEquals(events.size - 1, lastEose)
|
||||
// first sub stays within its limit (allow +1 for relay quirks)
|
||||
assertEquals(true, firstEose in 1..101)
|
||||
// second sub stays within its limit (allow +1 for relay quirks)
|
||||
assertEquals(true, (lastEose - firstEose - 1) in 1..11)
|
||||
// everything before the first EOSE is an event id
|
||||
assertEquals(true, events.take(firstEose).all { it.length == 64 })
|
||||
// everything between the two EOSEs is an event id
|
||||
assertEquals(true, events.subList(firstEose + 1, lastEose).all { it.length == 64 })
|
||||
}
|
||||
}
|
||||
|
||||
+9
-42
@@ -21,22 +21,17 @@
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.fetchAllPages
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientReqBypassingRelayLimitsTest : BaseNostrClientTest() {
|
||||
class NostrClientReqBypassingRelayLimitsTest : RelayClientTest() {
|
||||
@Test
|
||||
fun testDownloadFromRelayReturnsMetadataEvents() =
|
||||
runBlocking {
|
||||
@@ -50,32 +45,18 @@ class NostrClientReqBypassingRelayLimitsTest : BaseNostrClientTest() {
|
||||
pubKey = SyntheticEvents.hexId(it),
|
||||
)
|
||||
}
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(corpus)
|
||||
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(corpus)
|
||||
|
||||
val events = mutableListOf<Event>()
|
||||
|
||||
val totalFound =
|
||||
client.fetchAllPages(
|
||||
relay = "ws://127.0.0.1:7770/",
|
||||
filters =
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
relay = defaultRelayUrl,
|
||||
filters = listOf(Filter(kinds = listOf(MetadataEvent.KIND), limit = 1000)),
|
||||
) { event ->
|
||||
events.add(event)
|
||||
}
|
||||
|
||||
client.disconnect()
|
||||
delay(500)
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
|
||||
assertEquals(1000, totalFound)
|
||||
assertEquals(1000, events.size)
|
||||
events.forEach { event ->
|
||||
@@ -102,27 +83,18 @@ class NostrClientReqBypassingRelayLimitsTest : BaseNostrClientTest() {
|
||||
pubKey = SyntheticEvents.hexId(100_000 + it),
|
||||
)
|
||||
}
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(metadata + contacts)
|
||||
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(metadata + contacts)
|
||||
|
||||
val metadataEvents = mutableListOf<Event>()
|
||||
val contactListEvents = mutableListOf<Event>()
|
||||
|
||||
val totalFound =
|
||||
client.fetchAllPages(
|
||||
relay = "ws://127.0.0.1:7770/",
|
||||
relay = defaultRelayUrl,
|
||||
filters =
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 1000,
|
||||
),
|
||||
Filter(
|
||||
kinds = listOf(ContactListEvent.KIND),
|
||||
limit = 1500,
|
||||
),
|
||||
Filter(kinds = listOf(MetadataEvent.KIND), limit = 1000),
|
||||
Filter(kinds = listOf(ContactListEvent.KIND), limit = 1500),
|
||||
),
|
||||
) { event ->
|
||||
if (event.kind == MetadataEvent.KIND) {
|
||||
@@ -133,11 +105,6 @@ class NostrClientReqBypassingRelayLimitsTest : BaseNostrClientTest() {
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect()
|
||||
delay(500)
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
|
||||
assertEquals(2500, totalFound)
|
||||
assertEquals(1000, metadataEvents.size)
|
||||
assertEquals(1500, contactListEvents.size)
|
||||
|
||||
+5
-25
@@ -19,49 +19,29 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.publishAndConfirm
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientSendAndWaitTest : BaseNostrClientTest() {
|
||||
class NostrClientSendAndWaitTest : RelayClientTest() {
|
||||
@Test
|
||||
fun testSendAndWaitForResponse() =
|
||||
runBlocking {
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
|
||||
val randomSigner = NostrSignerInternal(KeyPair())
|
||||
|
||||
val event = randomSigner.sign(TextNoteEvent.build("Hello World"))
|
||||
|
||||
val relayA = "ws://127.0.0.1:7771/".normalizeRelayUrl()
|
||||
val relayB = "ws://127.0.0.1:7772/".normalizeRelayUrl()
|
||||
|
||||
val resultA =
|
||||
client.publishAndConfirm(
|
||||
event = event,
|
||||
relayList = setOf(relayA),
|
||||
)
|
||||
|
||||
val resultB =
|
||||
client.publishAndConfirm(
|
||||
event = event,
|
||||
relayList = setOf(relayB),
|
||||
)
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
val resultA = client.publishAndConfirm(event = event, relayList = setOf(relayA))
|
||||
val resultB = client.publishAndConfirm(event = event, relayList = setOf(relayB))
|
||||
|
||||
assertEquals(true, resultA)
|
||||
assertEquals(true, resultB)
|
||||
|
||||
+11
-39
@@ -19,19 +19,16 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.subscribeAsFlow
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
@@ -39,7 +36,7 @@ import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientSubscriptionAsFlowTest : BaseNostrClientTest() {
|
||||
class NostrClientSubscriptionAsFlowTest : RelayClientTest() {
|
||||
fun List<Event>.printDates(): String {
|
||||
val starting = this[0].createdAt
|
||||
return joinToString { (it.createdAt - starting).toString() }
|
||||
@@ -49,20 +46,12 @@ class NostrClientSubscriptionAsFlowTest : BaseNostrClientTest() {
|
||||
@Test
|
||||
fun testNostrClientSubscriptionAsFlow() =
|
||||
runTest {
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
SyntheticEvents.batch(20, kind = MetadataEvent.KIND),
|
||||
)
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(SyntheticEvents.batch(20, kind = MetadataEvent.KIND))
|
||||
|
||||
val flow =
|
||||
client.subscribeAsFlow(
|
||||
relay = "ws://127.0.0.1:7770/",
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 10,
|
||||
),
|
||||
relay = defaultRelayUrl,
|
||||
filter = Filter(kinds = listOf(MetadataEvent.KIND), limit = 10),
|
||||
)
|
||||
|
||||
var feedStates = listOf<Event>()
|
||||
@@ -79,11 +68,7 @@ class NostrClientSubscriptionAsFlowTest : BaseNostrClientTest() {
|
||||
advanceUntilIdle()
|
||||
}
|
||||
|
||||
job.cancel() // Cancel the collection job
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
job.cancel()
|
||||
|
||||
assertEquals(10, feedStates.size)
|
||||
}
|
||||
@@ -92,20 +77,12 @@ class NostrClientSubscriptionAsFlowTest : BaseNostrClientTest() {
|
||||
@Test
|
||||
fun testNostrClientSubscriptionAsFlowDebouncing() =
|
||||
runTest {
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
SyntheticEvents.batch(20, kind = MetadataEvent.KIND),
|
||||
)
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(SyntheticEvents.batch(20, kind = MetadataEvent.KIND))
|
||||
|
||||
val flow =
|
||||
client.subscribeAsFlow(
|
||||
relay = "ws://127.0.0.1:7770/",
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 10,
|
||||
),
|
||||
relay = defaultRelayUrl,
|
||||
filter = Filter(kinds = listOf(MetadataEvent.KIND), limit = 10),
|
||||
)
|
||||
|
||||
var feedStates = listOf<Event>()
|
||||
@@ -117,16 +94,11 @@ class NostrClientSubscriptionAsFlowTest : BaseNostrClientTest() {
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the test dispatcher to ensure emissions are processed
|
||||
while (feedStates.size < 10) {
|
||||
advanceUntilIdle()
|
||||
}
|
||||
|
||||
job.cancel() // Cancel the collection job
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
job.cancel()
|
||||
|
||||
assertEquals(10, feedStates.size)
|
||||
}
|
||||
|
||||
+5
-26
@@ -19,17 +19,13 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.StaticSubscription
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -37,15 +33,11 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientSubscriptionTest : BaseNostrClientTest() {
|
||||
class NostrClientSubscriptionTest : RelayClientTest() {
|
||||
@Test
|
||||
fun testNostrClientSubscription() =
|
||||
runBlocking {
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
SyntheticEvents.batch(150, kind = MetadataEvent.KIND),
|
||||
)
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(SyntheticEvents.batch(150, kind = MetadataEvent.KIND))
|
||||
|
||||
val resultChannel = Channel<Event>(UNLIMITED)
|
||||
val events = mutableSetOf<Event>()
|
||||
@@ -53,15 +45,7 @@ class NostrClientSubscriptionTest : BaseNostrClientTest() {
|
||||
val sub =
|
||||
StaticSubscription(
|
||||
client,
|
||||
mapOf(
|
||||
RelayUrlNormalizer.normalize("ws://127.0.0.1:7770/") to
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
),
|
||||
mapOf(defaultRelayUrl to listOf(Filter(kinds = listOf(MetadataEvent.KIND), limit = 100))),
|
||||
) { event ->
|
||||
assertEquals(MetadataEvent.KIND, event.kind)
|
||||
resultChannel.trySend(event)
|
||||
@@ -75,13 +59,8 @@ class NostrClientSubscriptionTest : BaseNostrClientTest() {
|
||||
}
|
||||
|
||||
resultChannel.close()
|
||||
|
||||
sub.close()
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
|
||||
assertEquals(100, events.size)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-40
@@ -19,19 +19,16 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay
|
||||
|
||||
import com.vitorpamplona.geode.fixtures.SyntheticEvents
|
||||
import com.vitorpamplona.geode.testing.RelayClientTest
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.fetchAsFlow
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
@@ -39,7 +36,7 @@ import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NostrClientSubscriptionUntilEoseAsFlowTest : BaseNostrClientTest() {
|
||||
class NostrClientSubscriptionUntilEoseAsFlowTest : RelayClientTest() {
|
||||
fun List<Event>.printDates(): String {
|
||||
val starting = this[0].createdAt
|
||||
return joinToString { (it.createdAt - starting).toString() }
|
||||
@@ -49,20 +46,12 @@ class NostrClientSubscriptionUntilEoseAsFlowTest : BaseNostrClientTest() {
|
||||
@Test
|
||||
fun testNostrClientSubscriptionUntilEoseAsFlow() =
|
||||
runTest {
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
SyntheticEvents.batch(20, kind = MetadataEvent.KIND),
|
||||
)
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(SyntheticEvents.batch(20, kind = MetadataEvent.KIND))
|
||||
|
||||
val flow =
|
||||
client.fetchAsFlow(
|
||||
relay = "ws://127.0.0.1:7770/",
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 10,
|
||||
),
|
||||
relay = defaultRelayUrl,
|
||||
filter = Filter(kinds = listOf(MetadataEvent.KIND), limit = 10),
|
||||
)
|
||||
|
||||
var feedStates = listOf<Event>()
|
||||
@@ -74,16 +63,11 @@ class NostrClientSubscriptionUntilEoseAsFlowTest : BaseNostrClientTest() {
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the test dispatcher to ensure emissions are processed
|
||||
while (feedStates.size < 10) {
|
||||
advanceUntilIdle()
|
||||
}
|
||||
|
||||
job.cancel() // Cancel the collection job
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
job.cancel()
|
||||
|
||||
assertEquals(10, feedStates.size)
|
||||
}
|
||||
@@ -92,20 +76,12 @@ class NostrClientSubscriptionUntilEoseAsFlowTest : BaseNostrClientTest() {
|
||||
@Test
|
||||
fun testNostrClientSubscriptionUntilEoseAsFlowDebouncing() =
|
||||
runTest {
|
||||
relayHub.getOrCreate("ws://127.0.0.1:7770/").preload(
|
||||
SyntheticEvents.batch(20, kind = MetadataEvent.KIND),
|
||||
)
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
val client = NostrClient(socketBuilder, appScope)
|
||||
defaultRelay.preload(SyntheticEvents.batch(20, kind = MetadataEvent.KIND))
|
||||
|
||||
val flow =
|
||||
client.fetchAsFlow(
|
||||
relay = "ws://127.0.0.1:7770/",
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 10,
|
||||
),
|
||||
relay = defaultRelayUrl,
|
||||
filter = Filter(kinds = listOf(MetadataEvent.KIND), limit = 10),
|
||||
)
|
||||
|
||||
var feedStates = listOf<Event>()
|
||||
@@ -117,16 +93,11 @@ class NostrClientSubscriptionUntilEoseAsFlowTest : BaseNostrClientTest() {
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the test dispatcher to ensure emissions are processed
|
||||
while (feedStates.size < 10) {
|
||||
advanceUntilIdle()
|
||||
}
|
||||
|
||||
job.cancel() // Cancel the collection job
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
relayHub.close()
|
||||
job.cancel()
|
||||
|
||||
assertEquals(10, feedStates.size)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user