Update sourceset folder names.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.lightning
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class Lud06Test {
|
||||
val lnTips =
|
||||
"LNURL1DP68GURN8GHJ7MRW9E6XJURN9UH8WETVDSKKKMN0WAHZ7MRWW4EXCUP0XPURXEFEX9SKGCT9V5ER2V33X4NRGP2NE42"
|
||||
|
||||
@Test()
|
||||
fun parseLnUrlp() {
|
||||
assertEquals("https://ln.tips/.well-known/lnurlp/0x3e91adaee25215f4", Lud06().toLnUrlp(lnTips))
|
||||
assertEquals("0x3e91adaee25215f4@ln.tips", Lud06().toLud16(lnTips))
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.hints
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.bloom.BloomFilterMurMur3
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class BloomFilterMurMur3Test {
|
||||
val testEncoded = "100:10:AKiEIEQKALgRACEABA==:3"
|
||||
val testInBinary = "00000000000101010010000100000100001000100101000000000000000111011000100000000000100001000000000000100000000000000000000000000000"
|
||||
|
||||
val key1 = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b".hexToByteArray()
|
||||
val key2 = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c".hexToByteArray()
|
||||
val key3 = "560c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c".hexToByteArray()
|
||||
|
||||
val keys =
|
||||
mutableListOf<ByteArray>().apply {
|
||||
for (seed in 0..1_000_000) {
|
||||
add(RandomInstance.bytes(32))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreate() {
|
||||
val bloomFilter = BloomFilterMurMur3(100, 10, commonSalt = 3)
|
||||
bloomFilter.add(key1)
|
||||
bloomFilter.add(key2)
|
||||
|
||||
assertEquals(testEncoded, bloomFilter.encode())
|
||||
assertEquals(testInBinary, bloomFilter.printBits())
|
||||
|
||||
assertTrue(bloomFilter.mightContain(key1))
|
||||
assertTrue(bloomFilter.mightContain(key2))
|
||||
|
||||
assertFalse(bloomFilter.mightContain(key3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDecoding() {
|
||||
val bloomFilter = BloomFilterMurMur3.decode(testEncoded)
|
||||
|
||||
assertEquals(testEncoded, bloomFilter.encode())
|
||||
assertEquals(testInBinary, bloomFilter.printBits())
|
||||
|
||||
assertTrue(bloomFilter.mightContain(key1))
|
||||
assertTrue(bloomFilter.mightContain(key2))
|
||||
|
||||
assertFalse(bloomFilter.mightContain(key3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProb() {
|
||||
val bloomFilter = BloomFilterMurMur3.decode(testEncoded)
|
||||
|
||||
var failureCounter = 0
|
||||
repeat(1_000_000) {
|
||||
if (bloomFilter.mightContain(RandomInstance.bytes(32))) {
|
||||
failureCounter++
|
||||
}
|
||||
}
|
||||
assertTrue(failureCounter <= 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProb2() {
|
||||
val bloomFilter = BloomFilterMurMur3(10_000_000, 4)
|
||||
keys.forEach(bloomFilter::add)
|
||||
|
||||
var failureCounter = 0
|
||||
repeat(1_000_000) {
|
||||
if (bloomFilter.mightContain(RandomInstance.bytes(32))) {
|
||||
failureCounter++
|
||||
}
|
||||
}
|
||||
assertTrue("Failures $failureCounter ${failureCounter / 1_000_000.0}", failureCounter / 1_000_000.0f < 0.015)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProb3() {
|
||||
val bloomFilter = BloomFilterMurMur3(10_000_000, 4)
|
||||
keys.forEach(bloomFilter::add)
|
||||
|
||||
var failureCounter = 0
|
||||
repeat(1_000_000) {
|
||||
if (bloomFilter.mightContain(RandomInstance.bytes(32))) {
|
||||
failureCounter++
|
||||
}
|
||||
}
|
||||
assertTrue("Failures $failureCounter ${failureCounter / 1_000_000.0}", failureCounter / 1_000_000.0f < 0.015)
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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.hints
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import com.vitorpamplona.quartz.utils.usedMemoryMb
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class HintIndexerTest {
|
||||
companion object {
|
||||
val VALID_CHARS: List<Char> = ('0'..'9') + ('a'..'z') + ('A'..'Z')
|
||||
|
||||
private fun randomChars(size: Int): String = CharArray(size) { VALID_CHARS.random() }.concatToString()
|
||||
|
||||
val keys =
|
||||
mutableListOf<HexKey>().apply {
|
||||
for (seed in 0..1_000_000) {
|
||||
add(RandomInstance.bytes(32).toHexKey())
|
||||
}
|
||||
}
|
||||
|
||||
val eventIds =
|
||||
mutableListOf<HexKey>().apply {
|
||||
for (seed in 0..1_000_000) {
|
||||
add(RandomInstance.bytes(32).toHexKey())
|
||||
}
|
||||
}
|
||||
|
||||
val addresses =
|
||||
keys.take(100_000).map {
|
||||
Address.assemble(
|
||||
RandomInstance.int(65_000),
|
||||
it,
|
||||
randomChars(10),
|
||||
)
|
||||
}
|
||||
|
||||
val relays =
|
||||
this::class.java
|
||||
.getResourceAsStream("relayDB.txt")
|
||||
?.readAllBytes()
|
||||
.toString()
|
||||
.split("\n")
|
||||
.mapNotNull { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
|
||||
val indexer by lazy {
|
||||
System.gc()
|
||||
Thread.sleep(1000)
|
||||
|
||||
val startingMemory = Runtime.getRuntime().usedMemoryMb()
|
||||
val result = HintIndexer()
|
||||
val endingMemory = Runtime.getRuntime().usedMemoryMb()
|
||||
|
||||
println("Filter using ${endingMemory - startingMemory}MB")
|
||||
|
||||
// Simulates 5 outbox relays for each key
|
||||
keys.forEach { key ->
|
||||
(0..5).map {
|
||||
result.addKey(key, relays.random())
|
||||
}
|
||||
}
|
||||
|
||||
// Simulates each event being in 8 + 0..10 relays.
|
||||
eventIds.forEach { id ->
|
||||
repeat(8 + RandomInstance.int(10)) {
|
||||
result.addEvent(id, relays.random())
|
||||
}
|
||||
}
|
||||
|
||||
// Simulates each address being in 8 + 0..10 relays.
|
||||
addresses.forEach { address ->
|
||||
repeat(8 + RandomInstance.int(10)) {
|
||||
result.addAddress(address, relays.random())
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
val testSize = 1000
|
||||
val testProb = 0.02f
|
||||
|
||||
fun assert99PercentSucess(success: () -> Boolean) {
|
||||
var failureCounter = 0
|
||||
repeat(testSize) {
|
||||
if (!success()) {
|
||||
failureCounter++
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
"Failure rate: $failureCounter of 1000 elements => ${(failureCounter / testSize.toFloat()) * 100}%",
|
||||
failureCounter / testSize.toFloat() < testProb,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProbExistingKeys() =
|
||||
assert99PercentSucess {
|
||||
indexer.hintsForKey(keys.random()).isNotEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProbNewKeys() =
|
||||
assert99PercentSucess {
|
||||
indexer.hintsForKey(RandomInstance.bytes(32)).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProbExistingEventIds() =
|
||||
assert99PercentSucess {
|
||||
indexer.hintsForEvent(eventIds.random()).isNotEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProbNewEventIds() =
|
||||
assert99PercentSucess {
|
||||
indexer.hintsForEvent(RandomInstance.bytes(32)).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProbExistingAddresses() =
|
||||
assert99PercentSucess {
|
||||
indexer.hintsForAddress(addresses.random()).isNotEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProbNewAddresses() =
|
||||
assert99PercentSucess {
|
||||
val newAddress =
|
||||
Address.assemble(
|
||||
RandomInstance.int(65000),
|
||||
RandomInstance.bytes(32).toHexKey(),
|
||||
randomChars(10),
|
||||
)
|
||||
|
||||
indexer.hintsForAddress(newAddress).isEmpty()
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.hints
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.bloom.MurmurHash3
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class MurMur3Test {
|
||||
class Case(
|
||||
val bytesHex: HexKey,
|
||||
val seed: Int,
|
||||
val result: Int,
|
||||
)
|
||||
|
||||
val testCases =
|
||||
listOf(
|
||||
Case("9fd4e9a905ca9e1a3086fa4c0a1ed829dbf18c15ec05af95c76b78d3d2f5651b", 886838366, -525456393),
|
||||
Case("e6c8f70f0d35a983bfebd00e5f29787c009c52971cfb4ac3a49b534b256b59cc", 1717487548, 1605080838),
|
||||
Case("7f7113833feb31e877f193e2fc75a64e9c70252c3ae3c73373ff34430ae40ea6", 1275582690, 225480992),
|
||||
Case("61770be6ec9df0f490743318e796e28ae34609732b61d365947871532d77d697", 514559346, 1424957638),
|
||||
Case("375f46b4687ba3cd035db303fa294d943816e64ca6b3adcda2ae40e8ac9d91a0", 1898708424, 1730418066),
|
||||
Case("c67044cd1d07a2aeb92b7bec973b6feb8abb9197840c59c101cacaa992489d49", 294602161, -1944496371),
|
||||
Case("49db4bfcc4da62e38c4076843cdde1425570806f09f121f5e7f2507c5ee1db85", 910710684, 944243368),
|
||||
Case("c5e98a30dead5ade4900b26eabae3435cfcdb64ff5e55c99641915a0c6ee73fc", 1107230285, 1550302684),
|
||||
Case("b0ed2e7568e6b4e1d5e5bab46fde01149331b824e48a281798d7216dde8f5890", 1013875681, -1265544300),
|
||||
Case("805f290e865bde094d77e82fb8b338d83347bc5449a4aed9fb08afb6a53a079b", 1674416787, -1821262025),
|
||||
// special cases
|
||||
Case("805f290e865bde094d77e82fb8b338d83347bc5449a4aed9fb08afb6a53a079b", Int.MAX_VALUE, -422576759),
|
||||
Case("805f290e865bde094d77e82fb8b338d83347bc5449a4aed9fb08afb6a53a079b", Int.MAX_VALUE + 1, 851385048),
|
||||
Case("805f290e865bde094d77e82fb8b338d83347bc5449a4aed9fb08afb6a53a079b", Int.MIN_VALUE, 851385048),
|
||||
Case("805f290e865bde094d77e82fb8b338d83347bc5449a4aed9fb08afb6a53a079b", 0, 1615518380),
|
||||
Case("fd", 1, 975430984),
|
||||
Case("00", 1, 0),
|
||||
Case("FF", 1, -797126820),
|
||||
Case("3033", 1, 1435178296),
|
||||
Case("0000", 1, -2047822809),
|
||||
Case("FFFF", 1, 1459517456),
|
||||
Case("3652a8", 1, 103723868),
|
||||
Case("000000", 1, 821347078),
|
||||
Case("FFFFFF", 1, -761438248),
|
||||
Case("00000000", 1, 2028806445),
|
||||
Case("FFFFFFFF", 1, 919009801),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testMurMur() {
|
||||
val hasher = MurmurHash3()
|
||||
testCases.forEach {
|
||||
assertEquals(
|
||||
it.result,
|
||||
hasher.hash(it.bytesHex.hexToByteArray(), it.seed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2539
File diff suppressed because it is too large
Load Diff
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.jackson
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import org.junit.Test
|
||||
|
||||
class EventDeserializerTest {
|
||||
@Test
|
||||
fun testEventTemplateToJson() {
|
||||
val templateJson = """{"created_at":1234,"kind":1,"tags":[],"content":"This is an unsigned event."}"""
|
||||
val template = EventTemplate.fromJson(templateJson)
|
||||
|
||||
val json = template.toJson()
|
||||
|
||||
assert(json == templateJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEventTemplate() {
|
||||
val templateJson = """{"kind":"1","content":"This is an unsigned event.","created_at":1234,"tags":[]}"""
|
||||
val template = EventTemplate.fromJson(templateJson)
|
||||
|
||||
assert(template.kind == 1)
|
||||
assert(template.content == "This is an unsigned event.")
|
||||
assert(template.tags.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSignedEvent() {
|
||||
val keyPair = KeyPair()
|
||||
val signer = NostrSignerInternal(keyPair)
|
||||
|
||||
val templateJson = """{"kind":"1","content":"This is an unsigned event.","created_at":1234,"tags":[]}"""
|
||||
val template = EventTemplate.fromJson(templateJson)
|
||||
|
||||
val signedEvent = signer.signerSync.sign(template)!!
|
||||
|
||||
assert(signedEvent.id.isNotEmpty())
|
||||
assert(signedEvent.sig.isNotEmpty())
|
||||
assert(signedEvent.pubKey.isNotEmpty())
|
||||
assert(signedEvent.verify())
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.jackson
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class InliningTagArrayPrettyPrinterTest {
|
||||
val mapper =
|
||||
jacksonObjectMapper().apply {
|
||||
setDefaultPrettyPrinter(InliningTagArrayPrettyPrinter())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val writer = mapper.writerWithDefaultPrettyPrinter()
|
||||
|
||||
val data =
|
||||
mapOf(
|
||||
"tags" to arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5, 6), intArrayOf(7, 8, 9)),
|
||||
)
|
||||
val expected =
|
||||
"""
|
||||
{
|
||||
"tags": [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9]
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
val json = writer.writeValueAsString(data)
|
||||
assertEquals(expected, json)
|
||||
|
||||
val data2 =
|
||||
mapOf(
|
||||
"tags" to arrayOf(arrayOf(intArrayOf(1, 2), intArrayOf(3, 4)), arrayOf(intArrayOf(5, 6), intArrayOf(7, 8))),
|
||||
)
|
||||
val expected2 =
|
||||
"""
|
||||
{
|
||||
"tags": [
|
||||
[[1, 2], [3, 4]],
|
||||
[[5, 6], [7, 8]]
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
val json2 = writer.writeValueAsString(data2)
|
||||
assertEquals(expected2, json2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEvent() {
|
||||
val nostrObject =
|
||||
"""
|
||||
{
|
||||
"id": "490d7439e530423f2540d4f2bdb73a0a2935f3df9e1f2a6f699a140c7db311fe",
|
||||
"pubkey": "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
"created_at": 1740669816,
|
||||
"kind": 0,
|
||||
"tags": [
|
||||
["alt", "User profile for Vitor"],
|
||||
["name", "Vitor"]
|
||||
],
|
||||
"content": "{\"name\":\"Vitor\"}",
|
||||
"sig": "977a6152199f17d103d8d56736ed1b7767054464cf9423d017c01c8cdd2344698f0a5e13da8dff98d01bb1f798837e3b6271e1fd1cac861bb90686f622ae6ef4"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val tree = mapper.readTree(nostrObject)
|
||||
|
||||
val prettified = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tree)
|
||||
|
||||
assertEquals(nostrObject, prettified)
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* 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.metadata
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventManualSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.utils.nsecToSigner
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class UpdateMetadataTest {
|
||||
val signer = "nsec10g0wheggqn9dawlc0yuv6adnat6n09anr7eyykevw2dm8xa5fffs0wsdsr".nsecToSigner()
|
||||
|
||||
/**
|
||||
* For debug purposes only
|
||||
*/
|
||||
fun Event.toPrettyJson(): String {
|
||||
val obj = EventManualSerializer.assemble(id, pubKey, createdAt, kind, tags, content, sig)
|
||||
return JsonMapper.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewMetadata() {
|
||||
val test = signer.sign(MetadataEvent.createNew("Vitor", createdAt = 1740669816))
|
||||
|
||||
val expected =
|
||||
"""
|
||||
{
|
||||
"id": "490d7439e530423f2540d4f2bdb73a0a2935f3df9e1f2a6f699a140c7db311fe",
|
||||
"pubkey": "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
"created_at": 1740669816,
|
||||
"kind": 0,
|
||||
"tags": [
|
||||
["alt", "User profile for Vitor"],
|
||||
["name", "Vitor"]
|
||||
],
|
||||
"content": "{\"name\":\"Vitor\"}",
|
||||
"sig": "977a6152199f17d103d8d56736ed1b7767054464cf9423d017c01c8cdd2344698f0a5e13da8dff98d01bb1f798837e3b6271e1fd1cac861bb90686f622ae6ef4"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
assertEquals(expected, test.toPrettyJson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateMetadata() {
|
||||
val test =
|
||||
signer.sign(
|
||||
MetadataEvent.createNew(
|
||||
name = "Vitor",
|
||||
displayName = "Vitor Pamplona",
|
||||
about = "Nostr's Chief Android Officer - #Amethyst",
|
||||
picture = "https://vitorpamplona.com/images/me_300.jpg",
|
||||
banner = "https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360",
|
||||
pronouns = "he/him",
|
||||
website = "https://vitorpamplona.com",
|
||||
nip05 = "_@vitorpamplona.com",
|
||||
lnAddress = "vitor@vitorpamplona.com",
|
||||
lnURL = "TEST",
|
||||
github = "https://gist.github.com/vitorpamplona/cf19e2d1d7f8dac6348ad37b35ec8421",
|
||||
createdAt = 1740669816,
|
||||
),
|
||||
)
|
||||
|
||||
val expected =
|
||||
"""
|
||||
{
|
||||
"id": "2b2761d66db4a83d5fb7a98cafb8414e9b0c238ceb67d729bedacc1a092d516f",
|
||||
"pubkey": "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
"created_at": 1740669816,
|
||||
"kind": 0,
|
||||
"tags": [
|
||||
["alt", "User profile for Vitor"],
|
||||
["name", "Vitor"],
|
||||
["display_name", "Vitor Pamplona"],
|
||||
["picture", "https://vitorpamplona.com/images/me_300.jpg"],
|
||||
["banner", "https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360"],
|
||||
["website", "https://vitorpamplona.com"],
|
||||
["pronouns", "he/him"],
|
||||
["about", "Nostr's Chief Android Officer - #Amethyst"],
|
||||
["nip05", "_@vitorpamplona.com"],
|
||||
["lud16", "vitor@vitorpamplona.com"],
|
||||
["lud06", "TEST"],
|
||||
["i", "github:vitorpamplona", "cf19e2d1d7f8dac6348ad37b35ec8421"]
|
||||
],
|
||||
"content": "{\"name\":\"Vitor\",\"display_name\":\"Vitor Pamplona\",\"picture\":\"https://vitorpamplona.com/images/me_300.jpg\",\"banner\":\"https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360\",\"website\":\"https://vitorpamplona.com\",\"pronouns\":\"he/him\",\"about\":\"Nostr's Chief Android Officer - #Amethyst\",\"nip05\":\"_@vitorpamplona.com\",\"lud16\":\"vitor@vitorpamplona.com\",\"lud06\":\"TEST\"}",
|
||||
"sig": "0a8c78eb0c5e0ba46e4781cc445fb7b6d275b434cead9231bd19b4f95671e3ab872264e50d4456d6036a84cc81e517bfa24229571519aabefcbce431e0c7163e"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
assertEquals(expected, test.toPrettyJson())
|
||||
|
||||
val expected2 =
|
||||
"""
|
||||
{
|
||||
"id": "2e1e57fae4e4baddac025ea0b49afc093f2aa27610a05e584184ed26b29d7590",
|
||||
"pubkey": "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
"created_at": 1740669817,
|
||||
"kind": 0,
|
||||
"tags": [
|
||||
["alt", "User profile for 2 Vitor"],
|
||||
["name", "2 Vitor"],
|
||||
["display_name", "2 Vitor Pamplona"],
|
||||
["picture", "2 https://vitorpamplona.com/images/me_300.jpg"],
|
||||
["banner", "2 https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360"],
|
||||
["website", "2 https://vitorpamplona.com"],
|
||||
["pronouns", "2 he/him"],
|
||||
["about", "2 Nostr's Chief Android Officer - #Amethyst"],
|
||||
["nip05", "2 _@vitorpamplona.com"],
|
||||
["lud16", "2 vitor@vitorpamplona.com"],
|
||||
["lud06", "2 TEST"],
|
||||
["i", "github:vitorpamplona", "2cf19e2d1d7f8dac6348ad37b35ec8421"]
|
||||
],
|
||||
"content": "{\"name\":\"2 Vitor\",\"display_name\":\"2 Vitor Pamplona\",\"picture\":\"2 https://vitorpamplona.com/images/me_300.jpg\",\"banner\":\"2 https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360\",\"website\":\"2 https://vitorpamplona.com\",\"pronouns\":\"2 he/him\",\"about\":\"2 Nostr's Chief Android Officer - #Amethyst\",\"nip05\":\"2 _@vitorpamplona.com\",\"lud16\":\"2 vitor@vitorpamplona.com\",\"lud06\":\"2 TEST\"}",
|
||||
"sig": "a25483bc0fcc79ccd337e3ff846351097109fc13f1cd1c9cc15f7f2ad46417aeb7c05c1524aeadbab93036fc0743c8c310a5b2b7b482e1808649b12a3ee29d49"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val test2 =
|
||||
signer.sign(
|
||||
MetadataEvent.updateFromPast(
|
||||
latest = test,
|
||||
name = "2 Vitor",
|
||||
displayName = "2 Vitor Pamplona",
|
||||
about = "2 Nostr's Chief Android Officer - #Amethyst",
|
||||
picture = "2 https://vitorpamplona.com/images/me_300.jpg",
|
||||
banner = "2 https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360",
|
||||
pronouns = "2 he/him",
|
||||
website = "2 https://vitorpamplona.com",
|
||||
nip05 = "2 _@vitorpamplona.com",
|
||||
lnAddress = "2 vitor@vitorpamplona.com",
|
||||
lnURL = "2 TEST",
|
||||
github = "https://gist.github.com/vitorpamplona/2cf19e2d1d7f8dac6348ad37b35ec8421",
|
||||
createdAt = 1740669817,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expected2, test2.toPrettyJson())
|
||||
|
||||
val expected3 =
|
||||
"""
|
||||
{
|
||||
"id": "94879b9a27fecf1337ede32013006ec4dfd5a3286a1e819abddfa1c3c132f008",
|
||||
"pubkey": "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
"created_at": 1740669817,
|
||||
"kind": 0,
|
||||
"tags": [
|
||||
["alt", "User profile for 2 Vitor"],
|
||||
["name", "2 Vitor"],
|
||||
["picture", "2 https://vitorpamplona.com/images/me_300.jpg"],
|
||||
["banner", "2 https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360"]
|
||||
],
|
||||
"content": "{\"name\":\"2 Vitor\",\"picture\":\"2 https://vitorpamplona.com/images/me_300.jpg\",\"banner\":\"2 https://pbs.twimg.com/profile_banners/15064756/1414451651/1080x360\"}",
|
||||
"sig": "729ee02364b4d429b6a66400ec850e80424602e3981ff2ad8a14d61bdee04f76ec68ddc4a0f38b0527f0218f7fa67668012a5c0f3c8ef943517b504040caa07e"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val test3 =
|
||||
signer.sign(
|
||||
MetadataEvent.updateFromPast(
|
||||
latest = test2,
|
||||
name = null,
|
||||
displayName = "",
|
||||
about = "",
|
||||
picture = null,
|
||||
banner = null,
|
||||
pronouns = "",
|
||||
website = "",
|
||||
nip05 = "",
|
||||
lnAddress = "",
|
||||
lnURL = "",
|
||||
github = "",
|
||||
createdAt = 1740669817,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expected3, test3.toPrettyJson())
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class RelayUrlFormatterTest {
|
||||
@Test
|
||||
fun format() {
|
||||
assertEquals("wss://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("wss://nostr.mom")?.url)
|
||||
assertEquals("wss://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("nostr.mom")?.url)
|
||||
assertEquals("ws://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("ws://nostr.mom")?.url)
|
||||
assertEquals("wss://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("wss://nostr.mom/")?.url)
|
||||
assertEquals("wss://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("https://nostr.mom/")?.url)
|
||||
assertEquals("ws://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("http://nostr.mom/")?.url)
|
||||
|
||||
assertEquals("wss://localhost:3030/", RelayUrlNormalizer.normalizeOrNull("wss://localhost:3030")?.url)
|
||||
assertEquals("ws://localhost:3030/", RelayUrlNormalizer.normalizeOrNull("localhost:3030")?.url)
|
||||
|
||||
assertEquals("wss://a.onion/", RelayUrlNormalizer.normalizeOrNull("wss://a.onion")?.url)
|
||||
assertEquals("ws://a.onion/", RelayUrlNormalizer.normalizeOrNull("a.onion")?.url)
|
||||
assertEquals("wss://a.onion/", RelayUrlNormalizer.normalizeOrNull("wss://a.onion/")?.url)
|
||||
assertEquals("ws://a.onion/", RelayUrlNormalizer.normalizeOrNull("a.onion/")?.url)
|
||||
|
||||
assertEquals("wss://nostr.mom/", RelayUrlNormalizer.normalizeOrNull("wss://nostr.mom")?.url)
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.store.sqlite
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import junit.framework.TestCase
|
||||
import org.junit.Test
|
||||
|
||||
class EventDbQueryAssemblerTest {
|
||||
val builder = EventIndexesModule(FullTextSearchModule())
|
||||
|
||||
val key1 = "7c5eb72a4584fdaaeaa145b25c92ea9917704224951219dbd43acef9e91fb88d"
|
||||
val key2 = "f3ac434d61bc0f491a814782ccfdf9c439dae1f0bde9097ad4a245f4c495cd14"
|
||||
val key3 = "12ae0fd81c85e1e7d9ed096397dc3129849425fe6f8afce7213ebf38ddfc6ca9"
|
||||
|
||||
@Test
|
||||
fun testEmpty() {
|
||||
val sql = builder.planQuery(Filter())
|
||||
TestCase.assertEquals(
|
||||
"SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers ORDER BY created_at DESC, id",
|
||||
sql,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLimit() {
|
||||
val sql = builder.planQuery(Filter(limit = 10))
|
||||
TestCase.assertEquals(
|
||||
"""
|
||||
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
|
||||
INNER JOIN (SELECT event_headers.row_id as row_id FROM event_headers WHERE 1 = 1 ORDER BY created_at DESC, id ASC LIMIT 10) AS filtered
|
||||
ON event_headers.row_id = filtered.row_id
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent(),
|
||||
sql,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllFearures() {
|
||||
val sql =
|
||||
builder.planQuery(
|
||||
listOf(
|
||||
Filter(limit = 10),
|
||||
Filter(authors = listOf(key1), kinds = listOf(1, 1111), search = "keywords", limit = 100),
|
||||
Filter(kinds = listOf(20), search = "cats", limit = 30),
|
||||
),
|
||||
)
|
||||
TestCase.assertEquals(
|
||||
"""
|
||||
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
|
||||
INNER JOIN (SELECT event_headers.row_id as row_id FROM event_headers WHERE 1 = 1 ORDER BY created_at DESC, id ASC LIMIT 10 UNION SELECT event_headers.row_id as row_id FROM event_headers INNER JOIN event_fts ON event_fts.event_header_row_id = event_headers.row_id WHERE (event_headers.kind IN (?, ?)) AND (event_headers.pubkey = ?) AND (event_fts MATCH ?) ORDER BY created_at DESC, id ASC LIMIT 100 UNION SELECT event_headers.row_id as row_id FROM event_headers INNER JOIN event_fts ON event_fts.event_header_row_id = event_headers.row_id WHERE (event_headers.kind = ?) AND (event_fts MATCH ?) ORDER BY created_at DESC, id ASC LIMIT 30) AS filtered
|
||||
ON event_headers.row_id = filtered.row_id
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent(),
|
||||
sql,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIdQuery() {
|
||||
val sql = builder.planQuery(Filter(ids = listOf(key1)))
|
||||
TestCase.assertEquals(
|
||||
"""
|
||||
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
|
||||
INNER JOIN (SELECT event_headers.row_id as row_id FROM event_headers WHERE event_headers.id = ?) AS filtered
|
||||
ON event_headers.row_id = filtered.row_id
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent(),
|
||||
sql,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAuthors() {
|
||||
val sql = builder.planQuery(Filter(authors = listOf(key1, key2)))
|
||||
TestCase.assertEquals(
|
||||
"""
|
||||
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
|
||||
INNER JOIN (SELECT event_headers.row_id as row_id FROM event_headers WHERE event_headers.pubkey IN (?, ?)) AS filtered
|
||||
ON event_headers.row_id = filtered.row_id
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent(),
|
||||
sql,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAuthorsAndSearch() {
|
||||
val sql = builder.planQuery(Filter(authors = listOf(key1, key2, key3), search = "keywords"))
|
||||
TestCase.assertEquals(
|
||||
"""
|
||||
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
|
||||
INNER JOIN (SELECT event_headers.row_id as row_id FROM event_headers INNER JOIN event_fts ON event_fts.event_header_row_id = event_headers.row_id WHERE (event_headers.pubkey IN (?, ?, ?)) AND (event_fts MATCH ?)) AS filtered
|
||||
ON event_headers.row_id = filtered.row_id
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent(),
|
||||
sql,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKindAndSearch() {
|
||||
val sql = builder.planQuery(Filter(kinds = listOf(1, 1111, 10000), search = "keywords"))
|
||||
TestCase.assertEquals(
|
||||
"""
|
||||
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
|
||||
INNER JOIN (SELECT event_headers.row_id as row_id FROM event_headers INNER JOIN event_fts ON event_fts.event_header_row_id = event_headers.row_id WHERE (event_headers.kind IN (?, ?, ?)) AND (event_fts MATCH ?)) AS filtered
|
||||
ON event_headers.row_id = filtered.row_id
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent(),
|
||||
sql,
|
||||
)
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.nip05DnsIdentifiers
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
class Nip05Test {
|
||||
companion object {
|
||||
private val ALL_UPPER_CASE_USER_NAME = "ONETWO"
|
||||
private val ALL_LOWER_CASE_USER_NAME = "onetwo"
|
||||
}
|
||||
|
||||
var nip05Verifier = Nip05()
|
||||
|
||||
@Test
|
||||
fun `test with matching case on user name`() =
|
||||
runBlocking {
|
||||
// Set-up
|
||||
val userNameToTest = ALL_UPPER_CASE_USER_NAME
|
||||
val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b"
|
||||
val nostrJson = "{\n \"names\": {\n \"$userNameToTest\": \"$expectedPubKey\" \n }\n}"
|
||||
val nip05 = "$userNameToTest@domain.com"
|
||||
|
||||
nip05Verifier.parseHexKeyFor(nip05, nostrJson).fold(
|
||||
onSuccess = { assertEquals(expectedPubKey, it) },
|
||||
onFailure = { fail("Test failure") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test with NOT matching case on user name`() =
|
||||
runBlocking {
|
||||
// Set-up
|
||||
val expectedPubKey = "ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b"
|
||||
val nostrJson = "{ \"names\": { \"$ALL_UPPER_CASE_USER_NAME\": \"$expectedPubKey\" }}"
|
||||
|
||||
val nip05 = "$ALL_LOWER_CASE_USER_NAME@domain.com"
|
||||
|
||||
nip05Verifier.parseHexKeyFor(nip05, nostrJson).fold(
|
||||
onSuccess = { assertEquals(expectedPubKey, it) },
|
||||
onFailure = { fail("Test failure") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute assemble url with invalid value returns null`() {
|
||||
// given
|
||||
val nip05address = "this@that@that.com"
|
||||
|
||||
// when
|
||||
val actualValue = nip05Verifier.assembleUrl(nip05address)
|
||||
|
||||
// then
|
||||
assertNull(actualValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute assemble url with valid value returns nip05 url`() {
|
||||
// given
|
||||
val userName = "TheUser"
|
||||
val domain = "domain.com"
|
||||
val nip05address = "$userName@$domain"
|
||||
val expectedValue = "https://$domain/.well-known/nostr.json?name=$userName"
|
||||
|
||||
// when
|
||||
val actualValue = nip05Verifier.assembleUrl(nip05address)
|
||||
|
||||
// then
|
||||
assertEquals(expectedValue, actualValue)
|
||||
}
|
||||
}
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
/**
|
||||
* 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.nip19Bech32
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NNote
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class NIP19ParserTest {
|
||||
@Test()
|
||||
fun uri_to_route_null() {
|
||||
val actual = Nip19Parser.uriToRoute(null)
|
||||
|
||||
Assert.assertEquals(null, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_unknown() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:unknown")
|
||||
|
||||
Assert.assertEquals(null, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_npub() {
|
||||
val actual =
|
||||
Nip19Parser.uriToRoute("nostr:npub1hv7k2s755n697sptva8vkh9jz40lzfzklnwj6ekewfmxp5crwdjs27007y")
|
||||
|
||||
Assert.assertTrue(actual?.entity is NPub)
|
||||
Assert.assertEquals(
|
||||
"bb3d6543d4a4f45f402b674ecb5cb2155ff12456fcdd2d66d9727660d3037365",
|
||||
(actual?.entity as? NPub)?.hex,
|
||||
)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_note() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute("nostr:note1stqea6wmwezg9x6yyr6qkukw95ewtdukyaztycws65l8wppjmtpscawevv")?.entity as? NNote
|
||||
|
||||
assertNotNull(result)
|
||||
Assert.assertEquals(
|
||||
"82c19ee9db7644829b4420f40b72ce2d32e5b7962744b261d0d53e770432dac3",
|
||||
result?.hex,
|
||||
)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_nprofile() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:nprofile")
|
||||
|
||||
Assert.assertEquals(null, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_incomplete_nevent() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:nevent")
|
||||
|
||||
Assert.assertEquals(null, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_incomplete_nrelay() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:nrelay")
|
||||
|
||||
Assert.assertEquals(null, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_incomplete_naddr() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:naddr")
|
||||
|
||||
Assert.assertEquals(null, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_complete_nprofile_2() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:nprofile1qqsyvrp9u6p0mfur9dfdru3d853tx9mdjuhkphxuxgfwmryja7zsvhqpzamhxue69uhhv6t5daezumn0wd68yvfwvdhk6tcpz9mhxue69uhkummnw3ezuamfdejj7qgwwaehxw309ahx7uewd3hkctcscpyug")
|
||||
|
||||
Assert.assertNotNull(actual)
|
||||
Assert.assertTrue(actual?.entity is NProfile)
|
||||
Assert.assertEquals("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", (actual?.entity as? NProfile)?.hex)
|
||||
Assert.assertEquals(NormalizedRelayUrl("wss://vitor.nostr1.com/"), (actual?.entity as? NProfile)?.relay?.first())
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_complete_nprofile() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:nprofile1qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7qgwwaehxw309ahx7uewd3hkctcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uqzq9thu3vem5gvsc6f3l3uyz7c92h6lq56t9wws0zulzkrgc6nrvym5jfztf")
|
||||
|
||||
Assert.assertTrue(actual?.entity is NProfile)
|
||||
Assert.assertEquals("1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b", (actual?.entity as? NProfile)?.hex)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_complete_nevent() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:nevent1qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7qgwwaehxw309ahx7uewd3hkctcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq36amnwvaz7tmjv4kxz7fwvd5xjcmpvahhqmr9vfejucm0d5hsz9mhwden5te0wfjkccte9ec8y6tdv9kzumn9wshsz8thwden5te0dehhxarj9ekh2arfdeuhwctvd3jhgtnrdakj7qg3waehxw309ucngvpwvcmh5tnfduhszythwden5te0dehhxarj9emkjmn99uq3jamnwvaz7tmhv4kxxmmdv5hxummnw3ezuamfdejj7qpqvsup5xk3e2quedxjvn2gjppc0lqny5dmnr2ypc9tftwmdxta0yjqrd6n50")
|
||||
|
||||
Assert.assertTrue(actual?.entity is NEvent)
|
||||
Assert.assertEquals("64381a1ad1ca81ccb4d264d48904387fc13251bb98d440e0ab4addb6997d7924", (actual?.entity as? NEvent)?.hex)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun uri_to_route_complete_naddr() {
|
||||
val actual = Nip19Parser.uriToRoute("nostr:naddr1qqyxzmt9w358jum5qyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypd7v3r24z33cydnk3fmlrd0exe5dlej3506zxs05q4puerp765mzqcyqqq8scsq6mk7u")
|
||||
|
||||
Assert.assertTrue(actual?.entity is NAddress)
|
||||
Assert.assertEquals("30818:5be6446aa8a31c11b3b453bf8dafc9b346ff328d1fa11a0fa02a1e6461f6a9b1:amethyst", (actual?.entity as? NAddress)?.aTag())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrParser() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute(
|
||||
"nostr:naddr1qqqqygzxpsj7dqha57pjk5k37gkn6g4nzakewtmqmnwryyhd3jfwlpgxtspsgqqqw4rs3xyxus",
|
||||
)
|
||||
assertEquals(
|
||||
"30023:460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c:",
|
||||
(result?.entity as? NAddress)?.aTag(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrParser2() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute(
|
||||
"nostr:naddr1qq8kwatfv3jj6amfwfjkwatpwfjqygxsm6lelvfda7qlg0tud9pfhduysy4vrexj65azqtdk4tr75j6xdspsgqqqw4rsg32ag8",
|
||||
)
|
||||
assertEquals(
|
||||
"30023:d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c:guide-wireguard",
|
||||
(result?.entity as? NAddress)?.aTag(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrParse3() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute(
|
||||
"naddr1qqyrswtyv5mnjv3sqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsygx3uczxts4hwue9ayfn7ggq62anzstde2qs749pm9tx2csuthhpjvpsgqqqw4rs8pmj38",
|
||||
)
|
||||
assertTrue(result?.entity is NAddress)
|
||||
assertEquals(
|
||||
"30023:d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193:89de7920",
|
||||
(result?.entity as? NAddress)?.aTag(),
|
||||
)
|
||||
assertEquals(NormalizedRelayUrl("wss://relay.damus.io/"), (result?.entity as? NAddress)?.relay?.get(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrATagParse3() {
|
||||
val address =
|
||||
ATag.parse(
|
||||
"30023:d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193:89de7920",
|
||||
"relay.damus.io",
|
||||
)
|
||||
assertEquals(30023, address?.kind)
|
||||
assertEquals(
|
||||
"d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193",
|
||||
address?.pubKeyHex,
|
||||
)
|
||||
assertEquals("89de7920", address?.dTag)
|
||||
assertEquals("wss://relay.damus.io/", address?.relay?.url)
|
||||
assertEquals(
|
||||
"naddr1qqyrswtyv5mnjv3sqy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7q3q68nqgewzkamnyh53x0epqrftkv2pdh9gzr6558v4vetzr3w7uxfsxpqqqp65wmpxfdu",
|
||||
address?.toNAddr(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrFormatter() {
|
||||
val address =
|
||||
ATag(
|
||||
30023,
|
||||
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
"",
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
"naddr1qqqqygzxpsj7dqha57pjk5k37gkn6g4nzakewtmqmnwryyhd3jfwlpgxtspsgqqqw4rs3xyxus",
|
||||
address.toNAddr(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrFormatter2() {
|
||||
val address =
|
||||
ATag(
|
||||
30023,
|
||||
"d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c",
|
||||
"guide-wireguard",
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
"naddr1qq8kwatfv3jj6amfwfjkwatpwfjqygxsm6lelvfda7qlg0tud9pfhduysy4vrexj65azqtdk4tr75j6xdspsgqqqw4rsg32ag8",
|
||||
address.toNAddr(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrFormatter3() {
|
||||
val address =
|
||||
ATag(
|
||||
30023,
|
||||
"d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193",
|
||||
"89de7920",
|
||||
RelayUrlNormalizer.normalizeOrNull("wss://relay.damus.io")!!,
|
||||
)
|
||||
assertEquals(
|
||||
"naddr1qqyrswtyv5mnjv3sqy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7q3q68nqgewzkamnyh53x0epqrftkv2pdh9gzr6558v4vetzr3w7uxfsxpqqqp65wmpxfdu",
|
||||
address.toNAddr(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrParserPablo() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"naddr1qq2hs7p30p6kcunxxamkgcnyd33xxve3veshyq3qyujphdcz69z6jafxpnldae3xtymdekfeatkt3r4qusr3w5krqspqxpqqqpaxjlg805f",
|
||||
)?.entity as? NAddress
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals(
|
||||
"31337:27241bb702d145a975260cfedee6265936dcd939eaecb88ea0e4071752c30402:xx1xulrf7wdbdlbc31far",
|
||||
result?.aTag(),
|
||||
)
|
||||
assertEquals(true, result?.relay?.isEmpty())
|
||||
assertEquals("27241bb702d145a975260cfedee6265936dcd939eaecb88ea0e4071752c30402", result?.author)
|
||||
assertEquals(31337, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrParserGizmo() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"naddr1qpqrvvfnvccrzdryxgunzvtxvgukge34xfjnqdpcv9sk2desxgmrscesvserzd3h8ycrywphvg6nsvf58ycnqef3v5mnsvt98pjnqdfs8ypzq3huhccxt6h34eupz3jeynjgjgek8lel2f4adaea0svyk94a3njdqvzqqqr4gudhrkyk",
|
||||
)?.entity as? NAddress
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals(
|
||||
"30023:46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d:613f014d2911fb9df52e048aae70268c0d216790287b5814910e1e781e8e0509",
|
||||
result?.aTag(),
|
||||
)
|
||||
assertEquals(true, result?.relay?.isEmpty())
|
||||
assertEquals("46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d", result?.author)
|
||||
assertEquals(30023, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nAddrParserGizmo2() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"naddr1qq9rzd3h8y6nqwf5xyuqygzxljlrqe027xh8sy2xtyjwfzfrxcll8afxh4hh847psjckhkxwf5psgqqqw4rsty50fx",
|
||||
)?.entity as? NAddress
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals(
|
||||
"30023:46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d:1679509418",
|
||||
result?.aTag(),
|
||||
)
|
||||
assertEquals(true, result?.relay?.isEmpty())
|
||||
assertEquals("46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d", result?.author)
|
||||
assertEquals(30023, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParserCompleteTest() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute("nostr:nevent1qqsdw6xpk28tjnrajz4xhy2jqg0md8ywxj6997rsutjzxs0207tedjspz4mhxue69uhhyetvv9ujumn0wd68ytnzvuhsygx2crjrydvqdksffurc0fdsfc566pxtrg78afw0v8kursecwdqg9vpsgqqqqqqsnknas6")?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("d768c1b28eb94c7d90aa6b9152021fb69c8e34b452f870e2e42341ea7f9796ca", result?.hex)
|
||||
assertEquals("wss://relay.nostr.bg/", result?.relay?.firstOrNull()?.url)
|
||||
assertEquals("cac0e43235806da094f0787a5b04e29ad04cb1a3c7ea5cf61edc1c338734082b", result?.author)
|
||||
assertEquals(1, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParserTest() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute("nostr:nevent1qqs0tsw8hjacs4fppgdg7f5yhgwwfkyua4xcs3re9wwkpkk2qeu6mhql22rcy")?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("f5c1c7bcbb8855210a1a8f2684ba1ce4d89ced4d8844792b9d60daca0679addc", result?.hex)
|
||||
assertEquals(true, result?.relay?.isEmpty())
|
||||
assertEquals(null, result?.author)
|
||||
assertEquals(null, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParser2Test() {
|
||||
val result =
|
||||
Nip19Parser.uriToRoute("nostr:nevent1qqsfvaa2w3nkw472lt2ezr6x5x347k8hht398vp7hrl6wrdjldry86sprfmhxue69uhhyetvv9ujuam9wd6x2unwvf6xxtnrdaks5myyah")?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("9677aa74676757cafad5910f46a1a35f58f7bae253b03eb8ffa70db2fb4643ea", result?.hex)
|
||||
assertEquals("wss://relay.westernbtc.com/", result?.relay?.firstOrNull()?.url)
|
||||
assertEquals(null, result?.author)
|
||||
assertEquals(null, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParser() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"nostr:nevent1qqstvrl6wftd8ht4g0vrp6m30tjs6pdxcvk977g769dcvlptkzu4ftqppamhxue69uhkummnw3ezumt0d5pzp78lz8r60568sd2a8dx3wnj6gume02gxaf92vx4fk67qv5kpagt6qvzqqqqqqygqr86c",
|
||||
)?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("b60ffa7256d3dd7543d830eb717ae50d05a6c32c5f791ed15b867c2bb0b954ac", result?.hex)
|
||||
assertEquals(
|
||||
NormalizedRelayUrl("wss://nostr.mom/"),
|
||||
result?.relay?.get(0),
|
||||
)
|
||||
assertEquals("f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a", result?.author)
|
||||
assertEquals(1, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParser2() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"nostr:nevent1qqsplpuwsgrrmq85rfup6w3w777rxmcmadu590emfx6z4msj2844euqpz3mhxue69uhhyetvv9ujuerpd46hxtnfdupzq3svyhng9ld8sv44950j957j9vchdktj7cxumsep9mvvjthc2pjuqvzqqqqqqye3a70w",
|
||||
)?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("1f878e82063d80f41a781d3a2ef7bc336f1beb7942bf3b49b42aee1251eb5cf0", result?.hex)
|
||||
assertEquals(NormalizedRelayUrl("wss://relay.damus.io/"), result?.relay?.get(0))
|
||||
assertEquals("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", result?.author)
|
||||
assertEquals(1, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParser3() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"nostr:nevent1qqsg6gechd3dhzx38n4z8a2lylzgsmmgeamhmtzz72m9ummsnf0xjfsppemhxue69uhkummn9ekx7mp0qy2hwumn8ghj7mn0wd68ytn00p68ytnyv4mz7qg4waehxw309aex2mrp0yhxummnw3ezucn89uqjqamnwvaz7tmwdaehgu3wv45kuatwv3a8wctw0f5kwtnnwpskxef0qythwumn8ghj7un9d3shjtnwdaehgu3wvfskuep0qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7dyp4wy",
|
||||
)?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("8d2338bb62db88d13cea23f55f27c4886f68cf777dac42f2b65e6f709a5e6926", result?.hex)
|
||||
assertEquals(
|
||||
"wss://nos.lol/,wss://nostr.oxtr.dev/,wss://relay.nostr.bg/,wss://nostr.einundzwanzig.space/,wss://relay.nostr.band/,wss://relay.damus.io/",
|
||||
result?.relay?.joinToString(",") { it.url },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventParserInvalidChecksum() {
|
||||
val result =
|
||||
Nip19Parser
|
||||
.uriToRoute(
|
||||
"nostr:nevent1qqsyxq8v0730nz38dupnjzp5jegkyz4gu2ptwcps4v32hjnrap0q0espz3mhxue69uhhyetvv9ujuerpd46hxtnfdupzq3svyhng9ld8sv44950j957j9vchdktj7cxumsep9mvvjthc2pjuqvzqqqqqqyn3t9gj",
|
||||
)?.entity as? NEvent
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("4300ec7fa2f98a276f033908349651620aa8e282b76030ab22abca63e85e07e6", result?.hex)
|
||||
assertEquals("wss://relay.damus.io/", result?.relay?.get(0)?.url)
|
||||
assertEquals("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", result?.author)
|
||||
assertEquals(1, result?.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventFormatter() {
|
||||
val nevent =
|
||||
NEvent.create(
|
||||
"f5c1c7bcbb8855210a1a8f2684ba1ce4d89ced4d8844792b9d60daca0679addc",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
assertEquals("nevent1qqs0tsw8hjacs4fppgdg7f5yhgwwfkyua4xcs3re9wwkpkk2qeu6mhql22rcy", nevent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventFormatterWithExtraInfo() {
|
||||
val nevent =
|
||||
NEvent.create(
|
||||
"f5c1c7bcbb8855210a1a8f2684ba1ce4d89ced4d8844792b9d60daca0679addc",
|
||||
"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194",
|
||||
40,
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
"nevent1qqs0tsw8hjacs4fppgdg7f5yhgwwfkyua4xcs3re9wwkpkk2qeu6mhqzypl62m6ad932k83u6sjwwkxrqq4cve0hkrvdem5la83g34m4rtqegqcyqqqqq2qh26va4",
|
||||
nevent,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nEventFormatterWithFullInfo() {
|
||||
val nevent =
|
||||
NEvent.create(
|
||||
"1f878e82063d80f41a781d3a2ef7bc336f1beb7942bf3b49b42aee1251eb5cf0",
|
||||
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
1,
|
||||
RelayUrlNormalizer.normalizeOrNull("wss://relay.damus.io"),
|
||||
)
|
||||
assertEquals(
|
||||
"nevent1qqsplpuwsgrrmq85rfup6w3w777rxmcmadu590emfx6z4msj2844euqpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsygzxpsj7dqha57pjk5k37gkn6g4nzakewtmqmnwryyhd3jfwlpgxtspsgqqqqqqsqvc0ku",
|
||||
nevent,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decodeBech32WithInvisibleCharacter() {
|
||||
val bomChar = '\uFEFF'
|
||||
val withBom = bomChar + "nsec1lfkarc7439n4l3uahr45ej8mrjc39dd879t0ps355550dj8j9uzs3rnw24"
|
||||
|
||||
assertEquals(
|
||||
"nsec1lfkarc7439n4l3uahr45ej8mrjc39dd879t0ps355550dj8j9uzs3rnw24",
|
||||
withBom.bechToBytes().toNsec(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.nip19Bech32
|
||||
|
||||
import com.vitorpamplona.quartz.nip19Bech32.tlv.to32BitByteArray
|
||||
import com.vitorpamplona.quartz.nip19Bech32.tlv.toInt32
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class TlvIntegerTest {
|
||||
fun to_int_32_length_smaller_than_4() {
|
||||
Assert.assertNull(byteArrayOfInts(1, 2, 3).toInt32())
|
||||
}
|
||||
|
||||
fun to_int_32_length_bigger_than_4() {
|
||||
Assert.assertNull(byteArrayOfInts(1, 2, 3, 4, 5).toInt32())
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun to_int_32_length_4() {
|
||||
val actual = byteArrayOfInts(1, 2, 3, 4).toInt32()
|
||||
|
||||
assertEquals(16909060, actual)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun backAndForth() {
|
||||
assertEquals(234, 234.to32BitByteArray().toInt32())
|
||||
assertEquals(1, 1.to32BitByteArray().toInt32())
|
||||
assertEquals(0, 0.to32BitByteArray().toInt32())
|
||||
assertEquals(1000, 1000.to32BitByteArray().toInt32())
|
||||
|
||||
assertEquals(-234, (-234).to32BitByteArray().toInt32())
|
||||
assertEquals(-1, (-1).to32BitByteArray().toInt32())
|
||||
assertEquals(-0, (-0).to32BitByteArray().toInt32())
|
||||
assertEquals(-1000, (-1000).to32BitByteArray().toInt32())
|
||||
}
|
||||
|
||||
private fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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.nip30CustomEmoji
|
||||
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class Nip30Test {
|
||||
@Test()
|
||||
fun parseEmoji() {
|
||||
val tags = mapOf(":soapbox:" to "http://soapbox")
|
||||
val input = "Alex Gleason :soapbox:"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, tags)
|
||||
|
||||
assertEquals(2, result!!.size)
|
||||
|
||||
assertEquals(
|
||||
"Alex Gleason ",
|
||||
(result[0] as CustomEmoji.TextType).text,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"http://soapbox",
|
||||
(result[1] as CustomEmoji.ImageUrlType).url,
|
||||
)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseEmojiInverted() {
|
||||
val tags = mapOf(":soapbox:" to "http://soapbox")
|
||||
val input = ":soapbox:Alex Gleason"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, tags)
|
||||
|
||||
assertEquals(2, result!!.size)
|
||||
|
||||
assertEquals(
|
||||
"http://soapbox",
|
||||
(result[0] as CustomEmoji.ImageUrlType).url,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"Alex Gleason",
|
||||
(result[1] as CustomEmoji.TextType).text,
|
||||
)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseEmoji2() {
|
||||
val tags =
|
||||
mapOf(
|
||||
":gleasonator:" to "http://gleasonator",
|
||||
":ablobcatrainbow:" to "http://ablobcatrainbow",
|
||||
":disputed:" to "http://disputed",
|
||||
)
|
||||
val input = "Hello :gleasonator: \uD83D\uDE02 :ablobcatrainbow: :disputed: yolo"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, tags)
|
||||
|
||||
assertEquals(7, result!!.size)
|
||||
|
||||
assertEquals("Hello ", (result[0] as CustomEmoji.TextType).text)
|
||||
|
||||
assertEquals("http://gleasonator", (result[1] as CustomEmoji.ImageUrlType).url)
|
||||
|
||||
assertEquals(" 😂 ", (result[2] as CustomEmoji.TextType).text)
|
||||
|
||||
assertEquals("http://ablobcatrainbow", (result[3] as CustomEmoji.ImageUrlType).url)
|
||||
|
||||
assertEquals(" ", (result[4] as CustomEmoji.TextType).text)
|
||||
|
||||
assertEquals("http://disputed", (result[5] as CustomEmoji.ImageUrlType).url)
|
||||
|
||||
assertEquals(" yolo", (result[6] as CustomEmoji.TextType).text)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseEmoji3() {
|
||||
val tags = emptyMap<String, String>()
|
||||
val input = "hello vitor: how can I help:"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, tags)
|
||||
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseEmoji4() {
|
||||
val tags = mapOf(":vitor:" to "http://vitor")
|
||||
val input = "hello :vitor: how :can I help:"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, tags)
|
||||
|
||||
assertEquals(3, result!!.size)
|
||||
|
||||
assertEquals("hello ", (result[0] as CustomEmoji.TextType).text)
|
||||
|
||||
assertEquals("http://vitor", (result[1] as CustomEmoji.ImageUrlType).url)
|
||||
|
||||
assertEquals(" how :can I help:", (result[2] as CustomEmoji.TextType).text)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseJapanese() {
|
||||
val tags = mapOf(":x30EDE:" to "http://x30EDE", ":\uD883\uDEDE:" to "http://\uD883\uDEDE")
|
||||
val input = "\uD883\uDEDE\uD883\uDEDE麺の:x30EDE:。:\uD883\uDEDE:(Violation of NIP-30)"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, tags)
|
||||
|
||||
assertEquals(3, result!!.size)
|
||||
|
||||
assertEquals("\uD883\uDEDE\uD883\uDEDE麺の", (result[0] as CustomEmoji.TextType).text)
|
||||
assertEquals("http://x30EDE", (result[1] as CustomEmoji.ImageUrlType).url)
|
||||
assertEquals("。:\uD883\uDEDE:(Violation of NIP-30)", (result[2] as CustomEmoji.TextType).text)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseJapanese2() {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("t", "ioメシヨソイゲーム"),
|
||||
arrayOf("emoji", "_ri", "https://media.misskeyusercontent.com/emoji/_ri.png"),
|
||||
arrayOf("emoji", "petthex_japanesecake", "https://media.misskeyusercontent.com/emoji/petthex_japanesecake.gif"),
|
||||
arrayOf("emoji", "ai_nomming", "https://media.misskeyusercontent.com/misskey/f6294900-f678-43cc-bc36-3ee5deeca4c2.gif"),
|
||||
arrayOf("proxy", "https://misskey.io/notes/9q0x6gtdysir03qh", "activitypub"),
|
||||
)
|
||||
val input =
|
||||
"\u200B:_ri:\u200B\u200B:_ri:\u200Bはベイクドモチョチョ\u200B:petthex_japanesecake:\u200Bを食べました\u200B:ai_nomming:\u200B\n" +
|
||||
"#ioメシヨソイゲーム\n" +
|
||||
"https://misskey.io/play/9g3qza4jow"
|
||||
|
||||
val result = CustomEmoji.assembleAnnotatedList(input, ImmutableListOfLists(tags))
|
||||
|
||||
assertEquals(9, result!!.size)
|
||||
|
||||
var i = 0
|
||||
assertEquals("\u200B", (result[i++] as CustomEmoji.TextType).text)
|
||||
assertEquals("https://media.misskeyusercontent.com/emoji/_ri.png", (result[i++] as CustomEmoji.ImageUrlType).url)
|
||||
assertEquals("\u200B\u200B", (result[i++] as CustomEmoji.TextType).text)
|
||||
assertEquals("https://media.misskeyusercontent.com/emoji/_ri.png", (result[i++] as CustomEmoji.ImageUrlType).url)
|
||||
assertEquals("\u200Bはベイクドモチョチョ\u200B", (result[i++] as CustomEmoji.TextType).text)
|
||||
assertEquals("https://media.misskeyusercontent.com/emoji/petthex_japanesecake.gif", (result[i++] as CustomEmoji.ImageUrlType).url)
|
||||
assertEquals("\u200Bを食べました\u200B", (result[i++] as CustomEmoji.TextType).text)
|
||||
assertEquals("https://media.misskeyusercontent.com/misskey/f6294900-f678-43cc-bc36-3ee5deeca4c2.gif", (result[i++] as CustomEmoji.ImageUrlType).url)
|
||||
assertEquals("\u200B\n#ioメシヨソイゲーム\nhttps://misskey.io/play/9g3qza4jow", (result[i] as CustomEmoji.TextType).text)
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.nip46RemoteSigner
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import org.junit.Test
|
||||
|
||||
class BunkerRequestTest {
|
||||
@Test
|
||||
fun testBunkerRequestDeSerialization() {
|
||||
val requestJson = """{"id":"123","method":"sign_event","params":["{\"created_at\":1234,\"kind\":1,\"tags\":[],\"content\":\"This is an unsigned event.\"}"]}"""
|
||||
val bunkerRequest = JsonMapper.mapper.readValue(requestJson, BunkerRequest::class.java)
|
||||
|
||||
assert(bunkerRequest is BunkerRequestSign)
|
||||
assert((bunkerRequest as BunkerRequestSign).event.kind == 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.nip51Lists
|
||||
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.list.tags.RoomIdTag
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Arrays
|
||||
|
||||
class TagArrayExt {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("group", "test", "wss://nos.lol"),
|
||||
arrayOf("group", "_", "wss://nos.lol"),
|
||||
)
|
||||
|
||||
val expectedTags =
|
||||
arrayOf(
|
||||
arrayOf("group", "test", "wss://nos.lol"),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testRemove() {
|
||||
assertTrue(
|
||||
Arrays.deepEquals(expectedTags, tags.remove(arrayOf("group", "_", "wss://nos.lol"))),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveParsing() {
|
||||
val removing = RoomId("_", RelayUrlNormalizer.normalize("wss://nos.lol"))
|
||||
assertTrue(
|
||||
Arrays.deepEquals(
|
||||
expectedTags,
|
||||
tags.removeParsing(RoomIdTag::parse, removing),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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.nip65RelayList
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class RelayListRecommendationProcessorTest {
|
||||
fun norm(str: String) = RelayUrlNormalizer.normalizeOrNull(str)!!
|
||||
|
||||
val userList =
|
||||
mutableMapOf(
|
||||
"User1" to mutableSetOf(norm("wss://relay1.com"), norm("wss://relay2.com"), norm("wss://relay3.com")),
|
||||
"User2" to mutableSetOf(norm("wss://relay4.com"), norm("wss://relay5.com"), norm("wss://relay6.com")),
|
||||
"User3" to mutableSetOf(norm("wss://relay1.com"), norm("wss://relay4.com"), norm("wss://relay6.com")),
|
||||
"User4" to mutableSetOf(norm("wss://relay2.com"), norm("wss://relay1.com"), norm("wss://relay4.com")),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testTranspose() {
|
||||
assertEquals(
|
||||
mapOf(
|
||||
norm("wss://relay1.com") to listOf("User1", "User3", "User4"),
|
||||
norm("wss://relay2.com") to listOf("User1", "User4"),
|
||||
norm("wss://relay3.com") to listOf("User1"),
|
||||
norm("wss://relay4.com") to listOf("User2", "User3", "User4"),
|
||||
norm("wss://relay5.com") to listOf("User2"),
|
||||
norm("wss://relay6.com") to listOf("User2", "User3"),
|
||||
).toString(),
|
||||
RelayListRecommendationProcessor.transpose(userList).toString(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProcessor() {
|
||||
val recommendations = RelayListRecommendationProcessor.reliableRelaySetFor(userList).toList()
|
||||
|
||||
val rec1 = recommendations[0]
|
||||
|
||||
assertEquals("wss://relay1.com/", rec1.relay.url)
|
||||
assertEquals(true, rec1.requiredToNotMissEvents)
|
||||
assertTrue("User1" in rec1.users)
|
||||
assertTrue("User2" !in rec1.users)
|
||||
assertTrue("User3" in rec1.users)
|
||||
assertTrue("User4" in rec1.users)
|
||||
|
||||
val rec2 = recommendations[1]
|
||||
|
||||
assertEquals("wss://relay4.com/", rec2.relay.url)
|
||||
assertEquals(true, rec2.requiredToNotMissEvents)
|
||||
assertTrue("User1" !in rec2.users)
|
||||
assertTrue("User2" in rec2.users)
|
||||
assertTrue("User3" !in rec2.users) // already included in relay 1
|
||||
assertTrue("User4" !in rec2.users) // already included in relay 1
|
||||
|
||||
val rec3 = recommendations[2]
|
||||
|
||||
assertEquals("wss://relay2.com/", rec3.relay.url)
|
||||
assertEquals(false, rec3.requiredToNotMissEvents)
|
||||
assertTrue("User1" in rec3.users)
|
||||
assertTrue("User2" !in rec3.users)
|
||||
assertTrue("User3" !in rec3.users)
|
||||
assertTrue("User4" !in rec3.users) // already included in relay 1 and 2
|
||||
|
||||
val rec4 = recommendations[3]
|
||||
|
||||
assertEquals("wss://relay5.com/", rec4.relay.url)
|
||||
assertEquals(false, rec4.requiredToNotMissEvents)
|
||||
assertTrue("User1" !in rec4.users)
|
||||
assertTrue("User2" in rec4.users)
|
||||
assertTrue("User3" !in rec4.users)
|
||||
assertTrue("User4" !in rec4.users)
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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.nip96FileStorage
|
||||
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfoParser
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class Nip96Test {
|
||||
val relativeUrlTest =
|
||||
"""
|
||||
{
|
||||
"api_url": "/n96",
|
||||
"download_url": "/",
|
||||
"content_types": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"audio/*"
|
||||
],
|
||||
"plans": {
|
||||
"free": {
|
||||
"name": "",
|
||||
"is_nip98_required": true,
|
||||
"max_byte_size": 5000000000
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val json =
|
||||
"""
|
||||
{
|
||||
"api_url": "https://nostr.build/api/v2/nip96/upload",
|
||||
"download_url": "https://media.nostr.build",
|
||||
"supported_nips": [
|
||||
94,
|
||||
96,
|
||||
98
|
||||
],
|
||||
"tos_url": "https://nostr.build/tos/",
|
||||
"content_types": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"audio/*"
|
||||
],
|
||||
"plans": {
|
||||
"free": {
|
||||
"name": "Free",
|
||||
"is_nip98_required": true,
|
||||
"url": "https://nostr.build",
|
||||
"max_byte_size": 26214400,
|
||||
"file_expiration": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"media_transformations": {
|
||||
"image": [
|
||||
"resizing",
|
||||
"format_conversion",
|
||||
"compression",
|
||||
"metadata_stripping"
|
||||
],
|
||||
"video": [
|
||||
"resizing",
|
||||
"format_conversion",
|
||||
"compression"
|
||||
]
|
||||
}
|
||||
},
|
||||
"professional": {
|
||||
"name": "Professional",
|
||||
"is_nip98_required": true,
|
||||
"url": "https://nostr.build/signup/new/",
|
||||
"max_byte_size": 10737418240,
|
||||
"file_expiration": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"media_transformations": {
|
||||
"image": [
|
||||
"resizing",
|
||||
"format_conversion",
|
||||
"compression",
|
||||
"metadata_stripping"
|
||||
],
|
||||
"video": [
|
||||
"resizing",
|
||||
"format_conversion",
|
||||
"compression"
|
||||
]
|
||||
}
|
||||
},
|
||||
"creator": {
|
||||
"name": "Creator",
|
||||
"is_nip98_required": true,
|
||||
"url": "https://nostr.build/signup/new/",
|
||||
"max_byte_size": 26843545600,
|
||||
"file_expiration": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"media_transformations": {
|
||||
"image": [
|
||||
"resizing",
|
||||
"format_conversion",
|
||||
"compression",
|
||||
"metadata_stripping"
|
||||
],
|
||||
"video": [
|
||||
"resizing",
|
||||
"format_conversion",
|
||||
"compression"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@Test()
|
||||
fun parseNostrBuild() {
|
||||
val info = ServerInfoParser().parse("https://nostr.build", json)
|
||||
|
||||
assertEquals("https://nostr.build/api/v2/nip96/upload", info.apiUrl)
|
||||
assertEquals("https://media.nostr.build", info.downloadUrl)
|
||||
assertEquals(listOf(94, 96, 98), info.supportedNips)
|
||||
assertEquals("https://nostr.build/tos/", info.tosUrl)
|
||||
assertEquals(listOf("image/*", "video/*", "audio/*"), info.contentTypes)
|
||||
|
||||
assertEquals(listOf("creator", "free", "professional"), info.plans.keys.sorted())
|
||||
|
||||
assertEquals("Free", info.plans["free"]?.name)
|
||||
assertEquals(true, info.plans["free"]?.isNip98Required)
|
||||
assertEquals("https://nostr.build", info.plans["free"]?.url)
|
||||
assertEquals(26214400L, info.plans["free"]?.maxByteSize)
|
||||
assertEquals(listOf(0, 0), info.plans["free"]?.fileExpiration)
|
||||
assertEquals(
|
||||
listOf("image", "video"),
|
||||
info.plans["free"]
|
||||
?.mediaTransformations
|
||||
?.keys
|
||||
?.sorted(),
|
||||
)
|
||||
|
||||
assertEquals(26843545600L, info.plans["creator"]?.maxByteSize)
|
||||
assertEquals(10737418240L, info.plans["professional"]?.maxByteSize)
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun parseRelativeUrls() {
|
||||
val info = ServerInfoParser().parse("https://test.com", relativeUrlTest)
|
||||
|
||||
assertEquals("https://test.com/n96", info.apiUrl)
|
||||
assertEquals("https://test.com/", info.downloadUrl)
|
||||
assertEquals(null, info.tosUrl)
|
||||
assertEquals(null, info.delegatedToUrl)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.utils
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import kotlin.random.Random
|
||||
|
||||
class HexEncodingTest {
|
||||
val testHex = "48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf"
|
||||
|
||||
@Test
|
||||
fun testHexEncodeDecodeOurs() {
|
||||
assertEquals(
|
||||
testHex,
|
||||
Hex.encode(
|
||||
Hex.decode(testHex),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsHex() {
|
||||
assertFalse("/0", Hex.isHex("/0"))
|
||||
assertFalse("/.", Hex.isHex("/."))
|
||||
assertFalse("::", Hex.isHex("::"))
|
||||
assertFalse("!!", Hex.isHex("!!"))
|
||||
assertFalse("@@", Hex.isHex("@@"))
|
||||
assertFalse("GG", Hex.isHex("GG"))
|
||||
assertFalse("FG", Hex.isHex("FG"))
|
||||
assertFalse("`a", Hex.isHex("`a"))
|
||||
assertFalse("gg", Hex.isHex("gg"))
|
||||
assertFalse("fg", Hex.isHex("fg"))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Test
|
||||
fun testRandomsIsHex() {
|
||||
for (i in 0..10000) {
|
||||
val bytes = Random.nextBytes(32)
|
||||
val hex = bytes.toHexString(HexFormat.Default)
|
||||
assertTrue(hex, Hex.isHex(hex))
|
||||
val hexUpper = bytes.toHexString(HexFormat.UpperCase)
|
||||
assertTrue(hexUpper, Hex.isHex(hexUpper))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Test
|
||||
fun testRandomsUppercase() {
|
||||
for (i in 0..1000) {
|
||||
val bytes = Random.nextBytes(32)
|
||||
val hex = bytes.toHexString(HexFormat.UpperCase)
|
||||
assertEquals(
|
||||
bytes.toList(),
|
||||
Hex.decode(hex).toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.utils
|
||||
|
||||
fun Runtime.usedMemoryMb(): Long {
|
||||
val totalMemoryMb = totalMemory() / (1024 * 1024)
|
||||
val freeMemoryMb = freeMemory() / (1024 * 1024)
|
||||
return totalMemoryMb - freeMemoryMb
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.utils
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.DeterministicSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
|
||||
|
||||
fun String.nsecToKeyPair() = KeyPair(this.bechToBytes())
|
||||
|
||||
fun String.nsecToSigner() = this.nsecToKeyPair().let { DeterministicSigner(it) }
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.utils
|
||||
|
||||
import junit.framework.TestCase
|
||||
import org.junit.Test
|
||||
|
||||
class StringUtilsTest {
|
||||
private val test =
|
||||
"""Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
|
||||
|
||||
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
|
||||
""".intern()
|
||||
|
||||
val atTheMiddle = DualCase("Lorem Ipsum".lowercase(), "Lorem Ipsum".uppercase())
|
||||
val atTheBeginning = DualCase("contrAry".lowercase(), "contrAry".uppercase())
|
||||
|
||||
val atTheEndCase = DualCase("h. rackham".lowercase(), "h. rackham".uppercase())
|
||||
|
||||
val lastCase =
|
||||
listOf(
|
||||
DualCase("my mom".lowercase(), "my mom".uppercase()),
|
||||
DualCase("my dad".lowercase(), "my dad".uppercase()),
|
||||
DualCase("h. rackham".lowercase(), "h. rackham".uppercase()),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun middleCaseOurs() {
|
||||
val list = listOf(atTheMiddle)
|
||||
TestCase.assertTrue(test.containsAny(list))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun atTheBeginningOurs() {
|
||||
val list = listOf(atTheBeginning)
|
||||
TestCase.assertTrue(test.containsAny(list))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun atTheEndOurs() {
|
||||
val list = listOf(atTheEndCase)
|
||||
TestCase.assertTrue(test.containsAny(list))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user