package com.bitcointxoko.gudariwallet.util import android.net.Uri import io.mockk.every import io.mockk.mockkStatic import io.mockk.unmockkStatic import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test class Bip21ParserTest { @Before fun setUp() { // Mock android.net.Uri.decode so tests run on the JVM without Android mockkStatic(Uri::class) // Default: pass-through (no encoding in most test inputs) every { Uri.decode(any()) } answers { firstArg() } } @After fun tearDown() { unmockkStatic(Uri::class) } // ------------------------------------------------------------------------- // Full BIP-21 URI // ------------------------------------------------------------------------- @Test fun `full URI with single param returns correct map`() { val result = Bip21Parser.parseParams("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf?amount=0.001") assertEquals(mapOf("amount" to "0.001"), result) } @Test fun `full URI with multiple params returns all params`() { val result = Bip21Parser.parseParams( "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf?amount=0.5&label=Foo&message=Bar" ) assertEquals( mapOf("amount" to "0.5", "label" to "Foo", "message" to "Bar"), result ) } @Test fun `full URI with no query string returns empty map`() { val result = Bip21Parser.parseParams("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf") assertTrue(result.isEmpty()) } @Test fun `full URI with empty query string after question mark returns empty map`() { val result = Bip21Parser.parseParams("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf?") assertTrue(result.isEmpty()) } // ------------------------------------------------------------------------- // Raw query string (no URI prefix) // ------------------------------------------------------------------------- @Test fun `bare query string with single param returns correct map`() { val result = Bip21Parser.parseParams("amount=0.001") assertEquals(mapOf("amount" to "0.001"), result) } @Test fun `bare query string with multiple params returns all params`() { val result = Bip21Parser.parseParams("amount=0.001&label=Satoshi") assertEquals(mapOf("amount" to "0.001", "label" to "Satoshi"), result) } // ------------------------------------------------------------------------- // URI-encoded values // ------------------------------------------------------------------------- @Test fun `percent-encoded value is decoded`() { every { Uri.decode("Hello%20World") } returns "Hello World" val result = Bip21Parser.parseParams("label=Hello%20World") assertEquals(mapOf("label" to "Hello World"), result) } @Test fun `percent-encoded value in full URI is decoded`() { every { Uri.decode("Thank%20You") } returns "Thank You" val result = Bip21Parser.parseParams( "bitcoin:addr?message=Thank%20You" ) assertEquals(mapOf("message" to "Thank You"), result) } // ------------------------------------------------------------------------- // Edge cases — blank / empty input // ------------------------------------------------------------------------- @Test fun `empty string returns empty map`() { val result = Bip21Parser.parseParams("") assertTrue(result.isEmpty()) } @Test fun `blank string returns empty map`() { val result = Bip21Parser.parseParams(" ") assertTrue(result.isEmpty()) } // ------------------------------------------------------------------------- // Malformed pairs // ------------------------------------------------------------------------- @Test fun `pair with no equals sign is skipped`() { // "label" has no '=', "amount=0.1" is valid val result = Bip21Parser.parseParams("label&amount=0.1") assertEquals(mapOf("amount" to "0.1"), result) } @Test fun `pair starting with equals sign (empty key) is skipped`() { // idx == 0, which is < 1, so this pair should be dropped val result = Bip21Parser.parseParams("=value&amount=0.1") assertEquals(mapOf("amount" to "0.1"), result) } @Test fun `all malformed pairs return empty map`() { val result = Bip21Parser.parseParams("noequalssign&=emptykey") assertTrue(result.isEmpty()) } // ------------------------------------------------------------------------- // Param with empty value // ------------------------------------------------------------------------- @Test fun `param with empty value is included with empty string`() { val result = Bip21Parser.parseParams("label=") assertEquals(mapOf("label" to ""), result) } // ------------------------------------------------------------------------- // Duplicate keys // ------------------------------------------------------------------------- @Test fun `duplicate keys — last value wins`() { // Kotlin's toMap() on a list of pairs keeps the last value for duplicates val result = Bip21Parser.parseParams("amount=0.001&amount=0.999") assertEquals(mapOf("amount" to "0.999"), result) } // ------------------------------------------------------------------------- // Lightning / BIP-21 extensions (lightning param) // ------------------------------------------------------------------------- @Test fun `lightning param is parsed correctly`() { val bolt11 = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx" every { Uri.decode(bolt11) } returns bolt11 val result = Bip21Parser.parseParams("amount=0.001&lightning=$bolt11") assertEquals(bolt11, result["lightning"]) } }