damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit f8245a7b0ebb6bec2784866081f599a742d9949b
parent 4036995348ea9738647d240fae8bb0319c3bade9
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Fri, 25 Jul 2025 18:43:45 -0700

Update Invoice tests to use the new blocks interface, and fix reverse blocks iteration indexing

Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>

Diffstat:
MdamusTests/InvoiceTests.swift | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mnostrdb/NdbBlock.swift | 9+++++++++
Mnostrdb/NonCopyableLinkedList.swift | 2+-
3 files changed, 159 insertions(+), 57 deletions(-)

diff --git a/damusTests/InvoiceTests.swift b/damusTests/InvoiceTests.swift @@ -21,90 +21,183 @@ final class InvoiceTests: XCTestCase { func testParseAnyAmountInvoice() throws { let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C" - let parsed = parse_note_content(content: .content(invstr,nil))!.blocks - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 1) - let invoiceOrNil = parsed[0].asInvoice - XCTAssertNotNil(invoiceOrNil) - guard let invoice = invoiceOrNil else { + + guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else { + XCTFail("Parsing threw an error") return } - XCTAssertEqual(invoice.amount, .any) - //XCTAssertEqual(invoice.expiry, 604800) - //XCTAssertEqual(invoice.created_at, 1666139119) - XCTAssertEqual(invoice.string, invstr) + + blockGroup.withList({ blockList in + XCTAssertEqual(blockList.count, 1) + let success: Bool? = blockList.useItem(at: 0, { block in + switch block { + case .invoice(let invoiceData): + guard let invoice = invoiceData.as_invoice() else { + XCTFail("Cannot get invoice from invoice block") + return false + } + XCTAssertEqual(invoice.amount, .any) + XCTAssertEqual(invoice.string, invstr) + return true + default: + XCTFail("Block is not an invoice") + return false + } + }) + XCTAssertEqual(success, true) + }) } func testTextAfterInvoice() throws { let invstr = """ -LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there -""" - let parsed = parse_note_content(content: .content(invstr,nil))!.blocks - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 2) - let invoiceOrNil = parsed[0].asInvoice - XCTAssertNotNil(invoiceOrNil) - XCTAssertEqual(parsed[1].asText, " hi there") - guard let invoice = invoiceOrNil else { + LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there + """ + + guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else { + XCTFail("Parsing threw an error") return } - XCTAssertEqual(invoice.amount, .any) - //XCTAssertEqual(invoice.expiry, 604800) - //XCTAssertEqual(invoice.created_at, 1666139119) + + blockGroup.withList({ blockList in + XCTAssertEqual(blockList.count, 2) + + // Check invoice block + let invoiceSuccess: Bool? = blockList.useItem(at: 0, { block in + switch block { + case .invoice(let invoiceData): + guard let invoice = invoiceData.as_invoice() else { + XCTFail("Cannot get invoice from invoice block") + return false + } + XCTAssertEqual(invoice.amount, .any) + return true + default: + XCTFail("First block is not an invoice") + return false + } + }) + XCTAssertEqual(invoiceSuccess, true) + + // Check text block + let textSuccess: Bool? = blockList.useItem(at: 1, { block in + switch block { + case .text(let text): + XCTAssertEqual(text.as_str(), " hi there") + return true + default: + XCTFail("Second block is not text") + return false + } + }) + XCTAssertEqual(textSuccess, true) + }) } func testParseInvoiceUpper() throws { let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R" - let parsed = parse_note_content(content: .content(invstr,nil))!.blocks - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 1) - let invoiceOrNil = parsed[0].asInvoice - XCTAssertNotNil(invoiceOrNil) - guard let invoice = invoiceOrNil else { + + guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else { + XCTFail("Parsing threw an error") return } - XCTAssertEqual(invoice.amount, .specific(10000)) - XCTAssertEqual(invoice.expiry, 604800) - XCTAssertEqual(invoice.created_at, 1666139119) - XCTAssertEqual(invoice.string, invstr) + + blockGroup.withList({ blockList in + XCTAssertEqual(blockList.count, 1) + let success: Bool? = blockList.useItem(at: 0, { block in + switch block { + case .invoice(let invoiceData): + guard let invoice = invoiceData.as_invoice() else { + XCTFail("Cannot get invoice from invoice block") + return false + } + XCTAssertEqual(invoice.amount, .specific(10000)) + XCTAssertEqual(invoice.expiry, 604800) + XCTAssertEqual(invoice.created_at, 1666139119) + XCTAssertEqual(invoice.string, invstr) + return true + default: + XCTFail("Block is not an invoice") + return false + } + }) + XCTAssertEqual(success, true) + }) } func testParseInvoiceWithPrefix() throws { let invstr = "lightning:lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" - let parsed = parse_note_content(content: .content(invstr,nil))!.blocks - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 1) - XCTAssertNotNil(parsed[0].asInvoice) + + guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else { + XCTFail("Parsing threw an error") + return + } + + blockGroup.withList({ blockList in + XCTAssertEqual(blockList.count, 1) + let success: Bool? = blockList.useItem(at: 0, { block in + switch block { + case .invoice(_): + return true + default: + XCTFail("Block is not an invoice") + return false + } + }) + XCTAssertEqual(success, true) + }) } func testParseInvoiceWithPrefixCapitalized() throws { let invstr = "LIGHTNING:LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R" - let parsed = parse_note_content(content: .content(invstr,nil))!.blocks - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 1) - XCTAssertNotNil(parsed[0].asInvoice) + + guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else { + XCTFail("Parsing threw an error") + return + } + + blockGroup.withList({ blockList in + XCTAssertEqual(blockList.count, 1) + let success: Bool? = blockList.useItem(at: 0, { block in + switch block { + case .invoice(_): + return true + default: + XCTFail("Block is not an invoice") + return false + } + }) + XCTAssertEqual(success, true) + }) } func testParseInvoice() throws { - let invstr = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" - let parsed = parse_note_content(content: .content(invstr,nil))!.blocks + let invstr = " lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 1) - let invoiceOrNil = parsed[0].asInvoice - XCTAssertNotNil(invoiceOrNil) - guard let invoice = invoiceOrNil else { + guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else { + XCTFail("Parsing threw an error") return } - XCTAssertEqual(invoice.amount, .specific(10000)) - XCTAssertEqual(invoice.expiry, 604800) - XCTAssertEqual(invoice.created_at, 1666139119) - XCTAssertEqual(invoice.string, invstr) + blockGroup.withList({ blockList in + XCTAssertEqual(blockList.count, 3) + let success: Bool? = blockList.useItem(at: 1, { block in + switch block { + case .invoice(let invoiceData): + guard let invoice = invoiceData.as_invoice() else { + XCTFail("Cannot get invoice from invoice block") + return false + } + XCTAssertEqual(invoice.amount, .specific(10000)) + XCTAssertEqual(invoice.expiry, 604800) + XCTAssertEqual(invoice.created_at, 1666139119) + XCTAssertEqual(invoice.string, invstr) + return true + default: + XCTFail("Block is not an invoice") + return false + } + }) + XCTAssertEqual(success, true) + }) } } diff --git a/nostrdb/NdbBlock.swift b/nostrdb/NdbBlock.swift @@ -139,6 +139,15 @@ struct NdbBlockGroup: ~Copyable { rawTextContent: content ) } + + /// Parses the note contents on-demand from a specific text. + static func parse(content: String) throws(NdbBlocksError) -> Self { + guard let metadata = BlocksMetadata.parseContent(content: content) else { throw NdbBlocksError.parseError } + return self.init( + metadata: .pure(metadata), + rawTextContent: content + ) + } } enum MaybeTxn<T: ~Copyable>: ~Copyable { diff --git a/nostrdb/NonCopyableLinkedList.swift b/nostrdb/NonCopyableLinkedList.swift @@ -38,7 +38,7 @@ struct NonCopyableLinkedList<T: ~Copyable>: ~Copyable { /// Iterates over each item of the list in reverse, with enumeration support. func forEachItemReversed<Y, E: Error>(_ borrowingFunction: ((_ index: Int, _ item: borrowing T) throws(E) -> LoopCommand<Y>)) throws(E) -> Y? { - var indexCounter = count + var indexCounter = count - 1 var cursor: Node? = self.tail outerLoop: while let nextItem = cursor {