From 739b13bfb2026032314482eac123c4fc49239c07 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 28 Aug 2026 15:23:04 -0700 Subject: [PATCH] Refactor poll actions: reduce CRAP/DRY, add property coverage Extract shared txid-action and poll-page bases in the client, a shared poll-read use-case base and a poll-txid iteration helper in the DB, and a shared poll-child record helper in the indexer. Reduce normalizePollCreateDatas complexity (CRAP 7.1 -> 5.0). Add property tests for poll services, handlers, and query filtering. Preserve behavior; all unit, acceptance, and property suites pass across client, DB, and indexer. By refactorer. --- psf-memo-client/src/services/hex.js | 13 +- .../src/services/memo-poll-option.js | 54 +---- .../src/services/memo-poll-vote.js | 54 +---- .../src/services/memo-txid-action.js | 50 +++++ .../src/services/poll-action-page.js | 55 +++++ .../src/services/poll-option-page.js | 44 +--- .../src/services/poll-vote-page.js | 46 ++--- .../property/poll-services.property.test.js | 162 +++++++++++++++ psf-memo-db/src/adapters/poll-query.js | 22 +- psf-memo-db/src/use-cases/get-poll-options.js | 23 +-- psf-memo-db/src/use-cases/get-poll-votes.js | 23 +-- psf-memo-db/src/use-cases/get-poll.js | 23 +-- .../src/use-cases/lib/poll-read-use-case.js | 32 +++ .../test/property/poll-query.property.test.js | 149 ++++++++++++++ .../src/use-cases/action-types/poll-child.js | 60 ++++++ .../src/use-cases/action-types/poll-create.js | 42 ++-- .../src/use-cases/action-types/poll-option.js | 40 +--- .../src/use-cases/action-types/poll-vote.js | 40 +--- .../property/poll-action.property.test.js | 192 ++++++++++++++++++ 19 files changed, 815 insertions(+), 309 deletions(-) create mode 100644 psf-memo-client/src/services/memo-txid-action.js create mode 100644 psf-memo-client/src/services/poll-action-page.js create mode 100644 psf-memo-client/test/property/poll-services.property.test.js create mode 100644 psf-memo-db/src/use-cases/lib/poll-read-use-case.js create mode 100644 psf-memo-db/test/property/poll-query.property.test.js create mode 100644 psf-memo-indexer/src/use-cases/action-types/poll-child.js create mode 100644 psf-memo-indexer/test/property/poll-action.property.test.js diff --git a/psf-memo-client/src/services/hex.js b/psf-memo-client/src/services/hex.js index 5ef989a..604b42b 100644 --- a/psf-memo-client/src/services/hex.js +++ b/psf-memo-client/src/services/hex.js @@ -25,4 +25,15 @@ function hexToBytes (hex, byteLength = 32, label = 'Value') { return bytes } -module.exports = { hexToBytes } +// Build the raw OP_RETURN payload for a txid-referencing Memo action: the +// given 32-byte txid followed by a UTF-8 encoded value. +function buildTxidTextPayload (txid, text) { + const txidBytes = hexToBytes(txid, 32, 'Poll txid') + const textBytes = new TextEncoder().encode(text) + const raw = new Uint8Array(txidBytes.length + textBytes.length) + raw.set(txidBytes, 0) + raw.set(textBytes, txidBytes.length) + return raw +} + +module.exports = { hexToBytes, buildTxidTextPayload } diff --git a/psf-memo-client/src/services/memo-poll-option.js b/psf-memo-client/src/services/memo-poll-option.js index 5b5ac8b..e947d43 100644 --- a/psf-memo-client/src/services/memo-poll-option.js +++ b/psf-memo-client/src/services/memo-poll-option.js @@ -6,25 +6,22 @@ prefix followed by the poll's 32-byte txid and the option text. The option text is limited to 184 bytes. - The wallet and an injected poll store are used so this module stays testable - and free of UI/network concerns; environmentally unsuitable I/O lives behind - those small adapter boundaries. + It shares the txid-embedding broadcast flow with MemoTxidAction and builds + its wire payload from the shared txid+text helper. Constants MEMO_ADD_POLL_OPTION_PREFIX : hex prefix for the Memo add-poll-option action (0x6d13) MAX_OPTION_BYTES : maximum option byte length (184) - POLL_TXID_BYTES : poll txid byte length (32) */ -const MemoAction = require('./memo-action') +const MemoTxidAction = require('./memo-txid-action') const { byteLength } = require('./utf8') -const { hexToBytes } = require('./hex') +const { buildTxidTextPayload } = require('./hex') const MEMO_ADD_POLL_OPTION_PREFIX = '6d13' const MAX_OPTION_BYTES = 184 -const POLL_TXID_BYTES = 32 -class MemoPollOption extends MemoAction { +class MemoPollOption extends MemoTxidAction { static config = { prefix: MEMO_ADD_POLL_OPTION_PREFIX, walletRequiredMsg: 'Memo poll option requires a wallet.', @@ -34,40 +31,14 @@ class MemoPollOption extends MemoAction { validationCode: 'poll_option_validation' } - constructor (deps = {}) { - super(deps) - this.pollTxid = deps.pollTxid || '' - this.polls = deps.polls || null - } - // A poll option is over-length when its UTF-8 byte count exceeds the limit. isTooLong (option) { return byteLength(option) > MAX_OPTION_BYTES } // Compose and broadcast a Memo add-poll-option action. - async add (option) { - const check = this.validate(option) - this._throwIfInvalid(check) - - if (!this.wallet) { - throw new Error(this.walletRequiredMsg) - } - - if (!this.pollTxid) { - const err = new Error('Poll txid is required.') - err.code = 'poll_option_validation' - throw err - } - - await this.wallet.getUtxos() - - const raw = buildAddPollOptionPayload(this.pollTxid, option) - const txid = await this.wallet.sendOpReturn(raw, this.prefix) - - this.reflect(txid, option) - - return txid + add (option) { + return this.broadcastTxid(option, buildTxidTextPayload) } // Record the new option on the injected poll store when one is present. @@ -83,17 +54,6 @@ class MemoPollOption extends MemoAction { } } -// Build the raw OP_RETURN message payload for an add-poll-option action. -// The protocol wire format is: